<?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: Oursky</title>
    <description>The latest articles on DEV Community by Oursky (@oursky).</description>
    <link>https://dev.to/oursky</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%2Forganization%2Fprofile_image%2F3626%2Fff6da174-23ce-4ad4-90c3-91ab92e35db9.png</url>
      <title>DEV Community: Oursky</title>
      <link>https://dev.to/oursky</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/oursky"/>
    <language>en</language>
    <item>
      <title>Parse Name and Address: Regex vs NER, with Code Examples</title>
      <dc:creator>Elliot Wong</dc:creator>
      <pubDate>Tue, 16 Mar 2021 12:17:13 +0000</pubDate>
      <link>https://dev.to/oursky/parse-name-and-address-regex-vs-ner-with-code-examples-3gdp</link>
      <guid>https://dev.to/oursky/parse-name-and-address-regex-vs-ner-with-code-examples-3gdp</guid>
      <description>&lt;p&gt;Here we have some regular expressions (regex) that can match a majority of names and addresses. Don't directly copy and paste them though, as there's no guarantee on always landing a 100% match only by using them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Name Regex
&lt;/h2&gt;

&lt;p&gt;We’re first discussing name regex intentionally, just because the it often includes a human name. It’d be clearer for you if we talk about names first.&lt;/p&gt;

&lt;p&gt;The regex here can be applied to a first or last name text field. We’ll focus on a data field for human name and ignore details differentiating first names and surnames.&lt;/p&gt;

&lt;p&gt;The pattern in more common names like “James,” “William,” “Elizabeth,” “Mary,” are trivial. They can be easily matched with . How about those with more variations? There are plenty of languages with different naming conventions. We’ll try to group different types of names into basic groups:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hyphenated names, e.g., Lloyd-Atkinson, Smith-Jones&lt;/li&gt;
&lt;li&gt;Names with apostrophes, e.g., D’Angelo, D’Esposito&lt;/li&gt;
&lt;li&gt;Names with spaces in-between, e.g., Van der Humpton, De Jong, Di Lorenzo&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Carry on reading to see how text extraction can be done with a regex.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;re&lt;/span&gt;

&lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s"&gt;"james"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="s"&gt;"william"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="s"&gt;"elizabeth"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="s"&gt;"mary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"d'angelo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"andy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"lloyd-atkinson"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"van der humpton"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"jo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;regex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s"&gt;'^[a-z ,.\'-]+$'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;],[]))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Address Regex
&lt;/h2&gt;

&lt;p&gt;For geographical or language reasons, the format of an address varies all over the world. Here’s a &lt;a href="https://en.wikipedia.org/wiki/Address"&gt;long list&lt;/a&gt; describing these formats per country.&lt;/p&gt;

&lt;p&gt;Since address format is too varied, it’s impossible for a regex to cover all these patterns. Even if there is one that manages to do so, it’d be very challenging to test, as the testing data set has to be more than enormous.&lt;/p&gt;

&lt;p&gt;Our regex for address will only cover some of the common ones in English-speaking countries. It should do the trick for addresses that start with a number, like “123 Sesame Street.” It’s from this &lt;a href="https://community.alteryx.com/t5/Alteryx-Designer-Discussions/RegEx-Addresses-different-formats-and-headaches/m-p/360176/highlight/true#M66106"&gt;discussion thread&lt;/a&gt; where it received positive feedback.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;re&lt;/span&gt;

&lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s"&gt;"224 Belmont Street APT 220"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"225 N Belmont St 220"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"123 west 2nd ave"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"4 Saffron Hill Road 1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;# will fail as they don't start with a digit
&lt;/span&gt;  &lt;span class="s"&gt;"Flat A, 2 Second Avenue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"Upper Level 10 ABC Street"&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;regex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s"&gt;'^(\d+) ?([A-Za-z](?= ))? (.*?) ([^ ]+?) ?((?&amp;lt;= )APT)? ?((?&amp;lt;= )\d*)?$'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;],[]))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Limitations of Using Regex to Extract Names and Addresses
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Dealing with Uncommon Values
&lt;/h3&gt;

&lt;p&gt;While these regexes may be able to validate a large portion of names and addresses, they will likely miss out on some, especially those that are non-English or newly introduced. For example, Spanish or German names weren’t considered thoroughly here, probably because the developer wasn’t familiar with these languages.&lt;/p&gt;

&lt;h3&gt;
  
  
  No Pattern to Follow
&lt;/h3&gt;

&lt;p&gt;Regex works well against data that has a strict pattern to follow, where neither name nor address belongs to a category. They’re ever-changing, with new instances created every day, along with a massive variation. Regex isn’t really going to do a good job on extracting them. In short, they are not “regular” enough with no intuitive patterns to follow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unable to Find the Likeliest Name
&lt;/h3&gt;

&lt;p&gt;Regex also lacks the ability to differentiate to find the “most likely” name. Let’s take a step back and assume there’s a regex R that can extract names flawlessly from documents that scanned via an OCR data extraction service. We want to get the recipient’s name (from a letter from Ann to Mary):&lt;br&gt;
&lt;/p&gt;

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

How have you been these days? Lately, Tom and I have been planning to travel around the World.
...
...
...

Love,
Ann
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are three names in the letter — Mary, Tom, and Ann. If you use R to find names, you’ll end up with a list of the three names, but you won’t be receiving just Mary, the recipient.&lt;/p&gt;

&lt;p&gt;So, how can this be achieved? We can give each name a score based on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Its position on the document&lt;/li&gt;
&lt;li&gt;How “naive” it is (i.e., how often it appeared in a training data set)&lt;/li&gt;
&lt;li&gt;Likelihood of a name to be the single target from a training data set&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Unable to Differentiate Name and Address
&lt;/h3&gt;

&lt;p&gt;On paper, names and address can be the same thing. “John” can be a name, or it can also be a part of an address, like “John Street”. Regexes don’t have the capability to see this difference and react accordingly. We surely don’t want to have results “Sesame Street” as a name and “Mr. Sherlock Holmes” as a street address!&lt;/p&gt;

&lt;p&gt;Well, how can I achieve a better extraction accuracy then? For more details and our proposed solution, please refer this &lt;a href="https://code.oursky.com/how-to-build-a-name-and-address-parser-regex-vs-named-entity-recognition-ner/"&gt;article&lt;/a&gt;! Cheers!&lt;/p&gt;

</description>
      <category>regex</category>
      <category>deeplearning</category>
      <category>machinelearning</category>
      <category>ner</category>
    </item>
    <item>
      <title>Regex for Date, Time and Currency, with Code Examples</title>
      <dc:creator>Elliot Wong</dc:creator>
      <pubDate>Wed, 03 Mar 2021 12:13:18 +0000</pubDate>
      <link>https://dev.to/oursky/regex-for-date-time-and-currency-2apm</link>
      <guid>https://dev.to/oursky/regex-for-date-time-and-currency-2apm</guid>
      <description>&lt;p&gt;In this article, regular expressions of currency (e.g., US$100, £0.12, or HK$54), time, and date are listed out for quick copy and paste. They’re all battle-tested. While each regex comes with limitations, we have notes addressing that along with customization tips.&lt;/p&gt;

&lt;p&gt;We do hope you check out the interactive code snippets to get a better idea on how the regexes work!&lt;/p&gt;

&lt;h2&gt;
  
  
  Currency Regex
&lt;/h2&gt;

&lt;p&gt;Note that currency signs apart from “$” will be dropped. The currency value will still gets matched, i.e., pound sterling sign £ in the first item of the test array.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;re&lt;/span&gt;

&lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s"&gt;"$9876 £112.00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"asdf$1234"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"$12.00 14"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"$3000000000000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"$00000000000001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"$00000000000000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"asdf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"one hundred forty two dollars"&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;regex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s"&gt;'\$?(?:(?:[1-9][0-9]{0,2})(?:,[0-9]{3})+|[1-9][0-9]*|0)(?:[\.,][0-9][0-9]?)?(?![0-9]+)'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;],[]))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Results should be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;['$9876', '112.00', '$1234', '$12.00', '14', '$3000000000000', '1', '0']
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Interactive code snippets available &lt;a href="https://repl.it/@elly0t/CurrencyRegex"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Time Regex
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;re&lt;/span&gt;
&lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s"&gt;"00:00:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"23:59:59"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"00 00 00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"23 59 59"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"00.00.00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"23.59.59"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"00:00.00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"23.59:59"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"9:00pm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"9:00am"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"10:00:00 am"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="s"&gt;"13:00:12 am"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"13 pm"&lt;/span&gt; &lt;span class="c1"&gt;#won't be considered as valid time
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;regex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s"&gt;'(?=((?: |^)[0-2]?\d[:. ]?[0-5]\d(?:[:. ]?[0-5]\d)?(?:[ ]?[ap]\.?m?\.?)?(?: |$)))'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;],[]))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Results should be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;['00:00:00', '23:59:59', '00 00 00', ' 00 00', '23 59 59', '00.00.00', '23.59.59', '00:00.00', '23.59:59', '9:00pm', '9:00am', '10:00:00 am', '13:00:12 am']
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Interactive code snippets available &lt;a href="https://repl.it/@elly0t/TimeRegex"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Regex Date with months in English (YYYY/MMMM/dd)
&lt;/h2&gt;

&lt;p&gt;Note that currency signs apart from “$” will be dropped. The currency value will still gets matched, i.e., pound sterling sign £ in the first item of the test array.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;re&lt;/span&gt;

&lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s"&gt;"2020-jan-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="s"&gt;"2012-jan-12"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"1920-feb-22"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;# space isn't a valid delimiter here, you can add it in the regex though
&lt;/span&gt;  &lt;span class="s"&gt;"2020 mar 1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;# only 19** and 20** are considered valid here, add year prefix accordingly, or extract with the last two year digits only
&lt;/span&gt;  &lt;span class="s"&gt;"1840-jun-12"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;# Must follow the format YYYY-MMMM-dd
&lt;/span&gt;  &lt;span class="s"&gt;"2020-01-01"&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;regex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;'(?=((?:(?:[0][1-9]|[1-2][0-9]|3[0-1]|[1-9])[/\-,.]?(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*[/\-,.]?(?:19|20)?\d{2}(?!\:)|'&lt;/span&gt;
    &lt;span class="s"&gt;'(?:19|20)?\d{2}[/\-,.]?(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*[/\-,.]?(?:[0][1-9]|[1-2][0-9]|3[0-1]|[1-9])|'&lt;/span&gt;
    &lt;span class="s"&gt;'(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*[/\-,.]?(?:[0][1-9]|[1-2][0-9]|3[0-1]|[1-9])[/\-,.]?(?:19|20)\d{2}(?!\:)|'&lt;/span&gt;
    &lt;span class="s"&gt;'(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*[/\-,.]?(?:[0][1-9]|[1-2][0-9]|3[0-1]|[1-9])[/\-,.]?\d{2})))'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;],[]))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Results should be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;['2020-jan-1', '20-jan-1', '2012-jan-12', '12-jan-12', '2-jan-12', '1920-feb-22', '20-feb-22', '40-jun-12']
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Interactive code snippets available &lt;a href="https://repl.it/@elly0t/DateRegexYYYYMMMMdd"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Check out the Original Post for More Details
&lt;/h2&gt;

&lt;p&gt;This is an abstract from our &lt;a href="https://code.oursky.com/regex-date-currency-and-time-how-to-extract-these-from-documnts-strings/"&gt;original blog post&lt;/a&gt;, which provides more regexes and explanations. In that article, more accurate ways to extract data are also discussed, with solutions proposed. It'd be nice if you can check it out and share your thoughts. Happy coding, cheers!&lt;/p&gt;

</description>
      <category>python</category>
      <category>regex</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Explaining Authentication Security Issues through Memes!</title>
      <dc:creator>Elliot Wong</dc:creator>
      <pubDate>Wed, 10 Feb 2021 08:19:41 +0000</pubDate>
      <link>https://dev.to/oursky/explaining-authentication-security-issues-through-memes-203</link>
      <guid>https://dev.to/oursky/explaining-authentication-security-issues-through-memes-203</guid>
      <description>&lt;h2&gt;
  
  
  Data not Hashed/Encrypted Properly
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oiCy8FFt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://code.oursky.com/wp-content/uploads/2021/02/Screenshot-2021-02-09-at-4.15.21-PM-1-scaled.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oiCy8FFt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://code.oursky.com/wp-content/uploads/2021/02/Screenshot-2021-02-09-at-4.15.21-PM-1-scaled.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Adopt algorithms/functions that are &lt;strong&gt;widely regarded as secure&lt;/strong&gt; when it comes to password hashing or data encryption. MD5 can be handy in many occasions, but it's not really a strong candidate to be used solely for encryption. &lt;/p&gt;

&lt;p&gt;For hashing, try &lt;strong&gt;argon2&lt;/strong&gt; or &lt;strong&gt;bcrypt&lt;/strong&gt;, as suggested by &lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#password-hashing-algorithms"&gt;OWASP&lt;/a&gt;. Those from the SHA family are alright too, be aware of the fact that they can be accelerated by a GPU though, making it more susceptible to brute-force attacks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hmm How to Identify a User when S/he Resets Password?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9tpns16i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://code.oursky.com/wp-content/uploads/2021/02/Screenshot-2021-02-10-at-12.36.57-PM-min-1-1160x651-min-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9tpns16i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://code.oursky.com/wp-content/uploads/2021/02/Screenshot-2021-02-10-at-12.36.57-PM-min-1-1160x651-min-1.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Please don't do this...&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Avoid using any personally identifiable information (PII).&lt;/strong&gt; Never take chances when it comes to PII. Even if encryption is applied, it can still be broken/decrypted by attackers, where they can then use the PII to match a user from your system.&lt;/p&gt;

&lt;p&gt;We’ve seen “encrypted” user IDs being used as the password reset token passed in a URL, which is not a very good idea, as aforementioned. In our case, the token encryption wasn’t done properly where a cryptographically broken algorithm (MD5) was adopted, which resulted in the quoted word to be encrypted in the last sentence.&lt;/p&gt;

&lt;p&gt;Always use randomly generated ID as the identifier. Give each generated ‘reset password’ session a life span and prevent brute-force matching attempts on the ID by implementing rate-limit mechanisms on the URL token.&lt;/p&gt;

&lt;h2&gt;
  
  
  No Expiry on Access Token
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4Sx40L04--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://code.oursky.com/wp-content/uploads/2021/02/Screenshot-2021-02-10-at-4.15.04-PM.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4Sx40L04--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://code.oursky.com/wp-content/uploads/2021/02/Screenshot-2021-02-10-at-4.15.04-PM.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s assume that you are generating access tokens properly with safe encryption. If there’s no expiry mechanism, the tokens that were already generated will haunt you forever. This is literally giving hackers unlimited time to pull off a token sidejacking. Just imagine an attacker getting their hands on an access token! They can authenticate themselves and go into your system and do whatever they want. This is quite likely to happen. Just open your cookies manager on your favorite browser and check how many access tokens are stored there.&lt;/p&gt;

&lt;p&gt;Even if your machine is kept safe and all transits are conducted with HTTPs, access token with no limited life span can still pose serious threats. Even if your connection is protected by HTTPs, with enough computing power (which isn’t hard to come by nowadays) and time, an attacker can intercept your exchanged data and crack your sessions/tokens out of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  For more details and in-depth solutions
&lt;/h2&gt;

&lt;p&gt;Read the &lt;a href="https://code.oursky.com/authentication-security-password-reset-and-more-best-practice/"&gt;original post&lt;/a&gt;, served with more memes!&lt;/p&gt;

</description>
      <category>authentication</category>
      <category>security</category>
      <category>algorithms</category>
    </item>
    <item>
      <title>Receipt Data Extraction with OCR, Regex and AI</title>
      <dc:creator>Elliot Wong</dc:creator>
      <pubDate>Wed, 10 Feb 2021 07:42:56 +0000</pubDate>
      <link>https://dev.to/oursky/receipt-data-extraction-with-ocr-regex-and-ai-170a</link>
      <guid>https://dev.to/oursky/receipt-data-extraction-with-ocr-regex-and-ai-170a</guid>
      <description>&lt;p&gt;Optional Image Recognition (OCR) is often the default option when it comes to document data extraction. Still, a OCR receipt scanner itself cannot yield accurate-enough results, therefore we have added Regular Expressions (Regex) and some Artificial Intelligence (AI) models to the formula.&lt;/p&gt;

&lt;p&gt;This article records our journey developing this final solution, which is now branded under the name &lt;a href="https://www.formx.ai/"&gt;FormX&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let’s start with a successful case first – FormX played an important role in streamlining the vetting process of a non-government organization’s disbursement program by enabling them to digitize data from images, forms, and physical documents from &lt;a href="https://www.linkedin.com/posts/googlehk_vision-ai-derive-image-insights-via-ml-activity-6609684625419837440-b4BE/"&gt;43,000&lt;/a&gt; applications.&lt;/p&gt;

&lt;p&gt;We will dive deep into parts where data is captured and extracted. While FormX can pull data off all kinds of physical forms and documents, for the sake of readability, general receipts will be used as a primary example throughout this article.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tCL1TL2G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.substack.com/image/fetch/w_1456%2Cc_limit%2Cf_auto%2Cq_auto:good%2Cfl_progressive:steep/https%253A%252F%252Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%252Fpublic%252Fimages%252Fd7b48ba3-059b-44b5-8817-b01629ef4dd4_1160x773.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tCL1TL2G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.substack.com/image/fetch/w_1456%2Cc_limit%2Cf_auto%2Cq_auto:good%2Cfl_progressive:steep/https%253A%252F%252Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%252Fpublic%252Fimages%252Fd7b48ba3-059b-44b5-8817-b01629ef4dd4_1160x773.jpeg" alt="Data extraction from physical receipts" width="880" height="586"&gt;&lt;/a&gt;Photo by &lt;a href="https://unsplash.com/@carlijeen?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Carli Jeen&lt;/a&gt; on &lt;a href="https://unsplash.com/"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Proposed Stages to Solve the Problem
&lt;/h2&gt;

&lt;p&gt;The foremost problem we want to solve here is how to extract {amount}, {date}, and {time} from various receipts.&lt;/p&gt;

&lt;p&gt;All sorts of receipts with different layouts exist out there, which make it challenging to extract just the amount, date and time. We came up with a solution that has four main stages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Get text data out from receipt images with OCR technology.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Filter outliers and group text data into horizontal lines.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Find candidates from horizontal lines.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Classify candidates with AI models and return positive ones.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Note that while Google Vision and other OCR providers out there consistently do a great job on turning a document image to an array of strings, accurate receipt data extraction requires a few more steps. Regex patterns filter out “candidates”, where only the most likely one of each target { date, time, total amount } are picked by the AI models.&lt;/p&gt;

&lt;p&gt;We spent a considerable amount of time in the fourth stage above by experimenting on AI models and tweaking parameters. However, we’d like to emphasize that pre-processing (stages 1 to 3) are equally important. They improve the quality of text data, which, in turn, improves the final classification result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Receipt OCR via Google Vision
&lt;/h2&gt;

&lt;p&gt;This is the first stage where a receipt image is converted to a collection of text with the aid of Google Vision API.&lt;/p&gt;

&lt;p&gt;Whether the image is for training AI models or is actually a receipt that will have its information extracted, it is always passed to Google’s Text Detection API to have its text recognized. It’s worth mentioning that – to enhance OCR accuracy, every image goes through a process of image warping first.&lt;/p&gt;

&lt;p&gt;The returned result is represented by five hierarchies in this descending scale order: Page, Block, Paragraph, Word, and Symbol.&lt;/p&gt;

&lt;p&gt;Each entity, no matter which hierarchy it belongs to, contains a text data and its bounding box (a collection of four vertices with x and y coordinates).&lt;/p&gt;

&lt;p&gt;We only used the two most basic ones, Word and Symbol. The former is an array of Symbols, while the latter represents a character or punctuation mark. You can find more detailed definitions of these hierarchies on Google’s official &lt;a href="https://cloud.google.com/vision/docs/fulltext-annotations"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Line Orientation Estimation
&lt;/h2&gt;

&lt;p&gt;By this time we have the texts from receipt images stored under the Word and Symbol entities.&lt;/p&gt;

&lt;p&gt;We will now group them into horizontal lines &lt;em&gt;relative&lt;/em&gt; to the receipt, sorted by the vertical offset of each from the top of receipt, stored as an array. Here’s the rationale behind it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Information in receipts is almost always horizontally printed. Text items on the same horizontal line are much more likely to be related.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It removes Words that aren’t horizontal enough. The output from OCR can sometimes contain some vertical items, which aren’t our target data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Different combinations of Words result in different meanings. Putting them together allows us to iterate through all possible ones.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Spacing between Words or Symbols is important. Once they are grouped within the same data instance, calculating the space length between them becomes easier.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Adjacent lines are also more likely to be related. To access them, we can simply move indices up and down as they are sorted instead of comparing the distance between a set of Words with another.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The images we receive can be captured with tilted angles, like the below one.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--j9bD7r2K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.substack.com/image/fetch/w_1456%2Cc_limit%2Cf_auto%2Cq_auto:good%2Cfl_progressive:steep/https%253A%252F%252Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%252Fpublic%252Fimages%252Fe88b6009-01b8-47b3-af33-00828194da6d_624x422.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--j9bD7r2K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.substack.com/image/fetch/w_1456%2Cc_limit%2Cf_auto%2Cq_auto:good%2Cfl_progressive:steep/https%253A%252F%252Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%252Fpublic%252Fimages%252Fe88b6009-01b8-47b3-af33-00828194da6d_624x422.png" alt="Pre-processing before Data Extraction - Line Estimation" width="624" height="422"&gt;&lt;/a&gt;Figure 1. Receipt Data Extraction from Relatively Horizontal Lines&lt;/p&gt;

&lt;p&gt;Let’s take the green lines shown in the Figure 1 as example. Apart from the lines being relatively horizontal, the date and time on each receipt are on the same line. Of course, this isn’t the case for every receipt.&lt;/p&gt;

&lt;p&gt;As a disclaimer, the example above is just a random image. In real life, receipts can be nowhere near as good and legible as we’d like them to be. For example, the receipt on the right receipt is covered. While we can accommodate tilted angles, we cannot see through covered information.&lt;/p&gt;

&lt;h2&gt;
  
  
  Grouping Words into Horizontal Lines, with RANSAC
&lt;/h2&gt;

&lt;p&gt;Each instance of Word comes with a set of four vertices, and with them is a vector of the Word which carries its direction. It can be calculated through the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KqNH5pAe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.substack.com/image/fetch/w_1456%2Cc_limit%2Cf_auto%2Cq_auto:good%2Cfl_progressive:steep/https%253A%252F%252Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%252Fpublic%252Fimages%252Fe532d64b-5af2-451a-947c-414f94067b67_624x299.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KqNH5pAe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.substack.com/image/fetch/w_1456%2Cc_limit%2Cf_auto%2Cq_auto:good%2Cfl_progressive:steep/https%253A%252F%252Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%252Fpublic%252Fimages%252Fe532d64b-5af2-451a-947c-414f94067b67_624x299.png" alt="Bounding Box Direction Calculation" width="624" height="299"&gt;&lt;/a&gt;Figure 2. Vector Direction of a Bounding Box&lt;/p&gt;

&lt;p&gt;All the Words’ vectors are computed and stored as a matrix. Now we need to determine whether they are horizontally on the same line. Calculating the distance between each Word’s vector and the average vector from all Words seems a good approach. If the distance lies within a threshold, it is horizontal enough; otherwise, the Word is thrown away. Once all the words are checked, the valid ones can be grouped into lines sorted with their vertical offsets (i.e., y coordinates).&lt;/p&gt;

&lt;p&gt;Although this method would filter out Words that are not horizontal enough, they may have already contaminated the calculation of the average vector. The filter process may end up as pointless, as the result wouldn’t be accurate.&lt;/p&gt;

&lt;p&gt;Fortunately, there is a saying – when we see outliers , we RANSAC them! RANdom SAmple Consensus (RANSAC) is an algorithm for robust-fitting a model in the presence of outliers, which, when implemented, will take them out (i.e., Words that don’t fit). To run a RANSAC, we will take the vector of each Word as one data item.&lt;/p&gt;

&lt;p&gt;Let’s say there’s a 70% chance to get one inlier (a value within a pattern) out of all Words by picking randomly. We have to be 99.99% sure that only inliers are picked according to this formula:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--h6Gt3S_8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.substack.com/image/fetch/w_1456%2Cc_limit%2Cf_auto%2Cq_auto:good%2Cfl_progressive:steep/https%253A%252F%252Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%252Fpublic%252Fimages%252Ff9827b2f-1c67-47d8-9d41-bcb753a73a10_624x321.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--h6Gt3S_8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.substack.com/image/fetch/w_1456%2Cc_limit%2Cf_auto%2Cq_auto:good%2Cfl_progressive:steep/https%253A%252F%252Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%252Fpublic%252Fimages%252Ff9827b2f-1c67-47d8-9d41-bcb753a73a10_624x321.png" alt="Get Inliers with RANSAC" width="624" height="321"&gt;&lt;/a&gt;Figure 3. Formula for Picking Inliers&lt;/p&gt;

&lt;p&gt;In Figure 3, the formula is where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;C&lt;/strong&gt; is the required confidence = 99.999%&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;r&lt;/strong&gt; is inlier chance = 70%&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;k&lt;/strong&gt; is the number of samples needed to fit a model, which is a vector in each run (i.e., one in each iteration)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;n&lt;/strong&gt; is the number of iteration needed to attain required confidence&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To visualize the formula better, put the numbers in and do the math. You will see that the number of iterations (&lt;strong&gt;n&lt;/strong&gt;) needed to have required confidence (&lt;strong&gt;c&lt;/strong&gt;) in getting an inlier is &amp;gt;= 10 times.&lt;/p&gt;

&lt;p&gt;In fact, 70% of inlier chance is pessimistic as the majority of Words on a receipt are horizontally printed. Setting this lower than the actual value ensures the outliers are eliminated. Plus, since we are picking one Word each time to check if it’s an inlier, k = 1.&lt;/p&gt;

&lt;p&gt;Based on the n value computed with RANSAC, we ran 10 iterations through the unprocessed Words yielding an array of Words where 99.999% of them got to be inliers. The average vector can then be calculated.&lt;/p&gt;

&lt;p&gt;Now we have an accurate average vector. Along with a threshold, we can calculate the distance of each Word’s vector against it to decide whether it is an inlier. Then all the inliers are grouped into horizontal lines with their y-axis values.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shortlisting Candidates with Amount, Time and Date regex
&lt;/h2&gt;

&lt;p&gt;Before we pass data to the AI classifier, we need to extract Candidates from the horizontal lines, mainly with regular expressions (regex). In this case, any text pattern that looks like price, date, or time will be considered as a candidate. Below is an example of regex for finding the amount and price candidates:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(?=((?:[^$0-9]|^)\$?(?:[1-9]\d{2}|[1-9]\d|[1-9])(?:,?\d{3})?(?:.\d{1,2})?))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let’s say there are two adjacent Words, 12/20 and 21/01/2020, in a horizontal line. The no-space candidate of concatenating the two is 12/2021/01/2020, which looks like a really messed up date and no one can tell what part is the year. If any part of this is the date we are seeking, we might end up missing it. The with-space version 12/20 21/01/2020 ensures the AI receives the separated Words, which will improve the chance of landing a match.&lt;/p&gt;

&lt;p&gt;At this stage, we realized regex can be a very handy tool to net some candidates. Consequently, a regex builder is available on FormX’s portal assisting users to come up with a correct regex for their target document.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Extraction with AI Binary Classifiers
&lt;/h2&gt;

&lt;p&gt;Three models have been trained for our respective needs: price, date, and time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Addressing the Flood of Useless Metadata
&lt;/h3&gt;

&lt;p&gt;Receipts often contain unwanted metadata like the grocery’s name and quantities of items purchased. If we simply train the classification model with an unprocessed dataset, the model will be extremely biased towards negative results and end up with an unbalanced dataset. To balance the dataset, we can multiply the data of amount, date, and time to a 1:1 ratio of positive and negative results.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bag-of-Words (BoW) Model
&lt;/h3&gt;

&lt;p&gt;A BoW model is employed to first classify texts. In a BoW model, a dictionary is built from words that appear in the receipt’s training dataset. If there are n unique words, the BoW model will be a vector with n dimensions.&lt;/p&gt;

&lt;p&gt;Normally, a BoW model records the occurrence of words, but we don’t in our case. Every word in classification data (i.e., receipt image copy) will be matched against the BoW model. If the word can’t be found in that dictionary, it will be ignored.&lt;/p&gt;

&lt;p&gt;For price data, the surrounding text on the same line will be computed against a BoW dictionary. If the current candidate doesn’t have the surrounding text matching the dictionary, they will be marked as false. For the others, the +/-1 lines are taken into account, as data on the date or time can reside across them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Amount Classifier
&lt;/h3&gt;

&lt;p&gt;The model we used for this is logistic regression (examining and describing the relationship between binary variables, such as pass/fail, win/lose, etc). These are the input parameters we used:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Position in Receipt.&lt;/strong&gt; The Words and Symbols come with a bounding box property. With that we can determine their vertical position divided by the total number of lines. It’s less likely to have a price right at the top of a receipt, so the candidates at lower positions have better likelihood.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Has Symbols.&lt;/strong&gt; For candidates, we check that symbols indicating price-related data exist in a pattern, such as “$”, “.”, and “,”.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Range Checking.&lt;/strong&gt; The numeric values in candidates are checked against a set of ranges like &amp;lt;10, &amp;gt;= 10, and &amp;lt;100, or an extreme one, like &amp;gt;= 10000000. Biases will be given based on the matching ranges. This can be tweaked based on the receipt. For example, if we’ve now extracted the amount from a bunch of receipts from a luxury brand, the range should be on the upper side of the scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  Date Classifier
&lt;/h3&gt;

&lt;p&gt;The model we used for this is random forest (an ensemble of randomized decision trees) with the number of estimators at 300. These are the input parameters we used:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Position in receipt.&lt;/strong&gt; This is calculated similarly to the Amount Classifier. Date usually shows up on the top or bottom, so candidates with a more central position have a reduced likeliness mark.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Has Symbols.&lt;/strong&gt; We check for symbols that imply date-related data, such as a slash (/) or period (.). Having less than two occurrences of these improves the candidate’s probability of being a date. Having a full year is also an advantage. A candidate that has “2019” in it, for example, is more likely to be a date than another one which has only “19”. Months in English is also a good indicator, and a fully spelled out month, like “September”, is a plus.&lt;/p&gt;

&lt;p&gt;Time and date are often printed on the same line or adjacent to each other, which we also take into consideration. Candidates with inconsistent delimiters will get penalized, such as 11/04-2019 over 11/04/2019. Some of the other factors we look at are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;1/(current year – extracted year + 1)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If the time candidate is on the same line or +/- one line&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If different separators are used&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Time Classifier
&lt;/h3&gt;

&lt;p&gt;The model we used for this is random forest with the number of estimators at 300. These are the input parameters we used:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Position in receipt.&lt;/strong&gt; This is calculated similarly to Date Classifier. Like Date, Time usually shows up on the top or bottom so candidates with a more central position have a reduced likeliness mark.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Has Symbols.&lt;/strong&gt; Candidates with “:” and empty space with less than 2 occurrences are more likely to be time. The ones with am or pm are also prime candidates. Similar to how Date is classified, candidates with Words that imply data related to Time will get extra marks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--l9w7_vRc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.substack.com/image/fetch/w_1456%2Cc_limit%2Cf_auto%2Cq_auto:good%2Cfl_progressive:steep/https%253A%252F%252Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%252Fpublic%252Fimages%252F47522f31-271f-487b-a216-68939def554d_1160x774.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--l9w7_vRc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.substack.com/image/fetch/w_1456%2Cc_limit%2Cf_auto%2Cq_auto:good%2Cfl_progressive:steep/https%253A%252F%252Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%252Fpublic%252Fimages%252F47522f31-271f-487b-a216-68939def554d_1160x774.jpeg" alt="" width="880" height="587"&gt;&lt;/a&gt;Photo by &lt;a href="https://unsplash.com/@alx_andru?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Alex&lt;/a&gt; on &lt;a href="https://unsplash.com/?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Future Plans for FormX
&lt;/h2&gt;

&lt;p&gt;Much like everything we do at Oursky, we are always on the lookout for improving our solutions and processes. Some are already in the works. We are looking to train the models with different inputs and parameters to improve accuracy. We also plan to expand our dataset. We’re currently collecting standard forms around the world, like insurance forms in the U.S.&lt;/p&gt;

&lt;p&gt;There are tons of very helpful AI researches and projects all over the world, and the amount of investment in them is awe-inspiring. We will definitely keep a lookout on them and integrate them if they prove to be innovative and outperform the current models.&lt;/p&gt;

&lt;p&gt;We’ll continue improving FormX so &lt;a href="https://us2.list-manage.com/subscribe?u=34db69ee3e01fe49e12302054&amp;amp;id=493e6df6f9"&gt;stay tuned&lt;/a&gt; for more of our explorations into the wonderful world of AI!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Addendum&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;This article has been updated on May 23, 5:25 p.m. HKT with key updates on the introduction, line orientation estimation, AI – binary classifier, and future plans for FormX. The updates are in line with our presentation of this topic in the &lt;a href="https://www.gdghk.org/2020/05/16/gdg-hong-kongs-ml-series-2020/"&gt;Google Developer Group Hong Kong ML Series 2020&lt;/a&gt;, an online event and series of learning sessions on machine learning. The webinar was presented as “&lt;strong&gt;How to extract 𝓧 from receipts?&lt;/strong&gt;”, which was held on May 23, 2020.&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;This article has been updated on October 15, 2020, 3:36 HKT with our official FormX brand/name.&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>machinelearning</category>
      <category>ai</category>
    </item>
    <item>
      <title>Kubernetes Security - Network Encryption between k8s Deployments and Ingress</title>
      <dc:creator>Elliot Wong</dc:creator>
      <pubDate>Wed, 03 Feb 2021 14:11:53 +0000</pubDate>
      <link>https://dev.to/oursky/kubernetes-security-network-encryption-between-k8s-deployments-and-ingress-ld7</link>
      <guid>https://dev.to/oursky/kubernetes-security-network-encryption-between-k8s-deployments-and-ingress-ld7</guid>
      <description>&lt;p&gt;By Calvin, who refuses to create a dev.to account.&lt;/p&gt;

&lt;p&gt;TL; DR: With a simple example here, we demonstrate how to secure connections between your Kubernetes (k8s) deployments and ingress by enabling TLS and HTTPS. This can be a critical part in your DevSecOps workf low or a business requirement your development team must accomplish.&lt;/p&gt;

&lt;h1&gt;
  
  
  Kubernetes Tutorial on Securing Connections
&lt;/h1&gt;

&lt;p&gt;This a quick how-to guide on hardening a k8s application by enforcing secure communication between an Ingress controller and other k8s services. This is important especially if your business requirements, like in financial services or enterprise environments, compel you to enforce strict security measures such as encrypting all traffic in transit.&lt;/p&gt;

&lt;p&gt;Some caveats: Managing a Kubernetes cluster itself is complex enough, and securing it can be convoluted. This will add another layer of complexity, so consider what your actual requirements are and conduct risk assessments. Not all projects require this level of security. Below is a simple visualization of traffic between Ingress and back end services.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
        ╔═════════════════════════╗       ╔════════════════════╗
 https  ║ ingress                 ║ https ║ backend            ║
 ───&amp;gt;───╫─────────────────────────╫───&amp;gt;───╫────────────────────╢
        ║ demo.some-cluster.com   ║       ║ demo-app           ║
        ╚═════════════════════════╝       ╚════════════════════╝
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  What’s not covered in this Kubernetes Security how-to
&lt;/h1&gt;

&lt;p&gt;This guide only walks you through strengthening the connection between an Ingress and a k8s service. Say you have collection of microservices, you may also want to secure the connection between every one of them as well. Below are a few suggestions, weigh them accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Route all traffic with Ingress
&lt;/h2&gt;

&lt;p&gt;Calls from a backend app to another must be routed through the Ingress. Connection is secured as we have already implemented TLS between the Ingress and the service(s) pointing to target backend app(s).&lt;/p&gt;

&lt;p&gt;This approach does have one downside though, where communication points of all backend apps are exposed. IP whitelisting and using internal headers are some measures to protect them exposed endpoints.&lt;/p&gt;

&lt;h2&gt;
  
  
  Encrypted connection for each app by implementing TLS
&lt;/h2&gt;

&lt;p&gt;With this one you will have to implement TLS and manage the corresponding certificate for each backend app. There can be a lot of chores just to generate the certificates, though this one completely avoids the exposed port issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrate service mesh
&lt;/h2&gt;

&lt;p&gt;You can install a service mesh like Linkerd or istio. What’s a service mesh? Basically it takes your yaml files and does some rewriting based on your instructions (e.g. some istio commands). With these amended config files, your k8s cluster will be deployed with some extra proxy services that intercept all communication between microservices and have security measures applied.&lt;/p&gt;

&lt;h1&gt;
  
  
  Prerequisites and assumptions
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;You are familiar with concepts of containers, Docker.&lt;/li&gt;
&lt;li&gt;Basic understandings on Kubernetes and how it achieves container orchestration are also required.&lt;/li&gt;
&lt;li&gt;You have the fundamentals like “https vs http” or “TLS vs SSL” and know how to generate a self-signed certificate.&lt;/li&gt;
&lt;/ul&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%2Fimages.unsplash.com%2Fphoto-1550751827-4bd374c3f58b%3Fixid%3DMXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%253D%26ixlib%3Drb-1.2.1%26auto%3Dformat%26fit%3Dcrop%26w%3D1000%26q%3D80" 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%2Fimages.unsplash.com%2Fphoto-1550751827-4bd374c3f58b%3Fixid%3DMXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%253D%26ixlib%3Drb-1.2.1%26auto%3Dformat%26fit%3Dcrop%26w%3D1000%26q%3D80" alt="Kubernetes Security via Application-level Network Encrpytion"&gt;&lt;/a&gt;Photo by &lt;a href="https://unsplash.com/@adigold1" rel="noopener noreferrer"&gt;Adi Goldstein&lt;/a&gt; on &lt;a href="https://unsplash.com" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Components in our example
&lt;/h1&gt;

&lt;p&gt;The example is made of these Kubernetes components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An Ingress where SSL termination for the public-facing domain, such as &lt;code&gt;secure-demo.some-cluster.com&lt;/code&gt; is set.&lt;/li&gt;
&lt;li&gt;A k8s Service, routing to our backend.&lt;/li&gt;
&lt;li&gt;A k8s Deployment a.k.a our backend, a nginx web server serving HTTPS.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Sample Kubernetes Configuration Files
&lt;/h2&gt;

&lt;p&gt;Here’s a configuration file named &lt;code&gt;backend.yaml&lt;/code&gt;, covering our entire backend (nginx server, a config map and a service). By providing the certs, we are done with the TLS Security Settings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConfigMap&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;demo-app&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-conf&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;site.conf&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;server {&lt;/span&gt;
      &lt;span class="s"&gt;listen 443 ssl;&lt;/span&gt;
      &lt;span class="s"&gt;server_name demo-app;&lt;/span&gt;
      &lt;span class="s"&gt;ssl_certificate /run/secrets/nginx-cert/tls.crt;&lt;/span&gt;
      &lt;span class="s"&gt;ssl_certificate_key /run/secrets/nginx-cert/tls.key;&lt;/span&gt;
      &lt;span class="s"&gt;location / {&lt;/span&gt;
        &lt;span class="s"&gt;root   /usr/share/nginx/html;&lt;/span&gt;
        &lt;span class="s"&gt;index  index.html index.htm;&lt;/span&gt;
        &lt;span class="s"&gt;try_files $uri $uri/ /index.html;&lt;/span&gt;
      &lt;span class="s"&gt;}&lt;/span&gt;
    &lt;span class="s"&gt;}&lt;/span&gt;
&lt;span class="s"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;demo-app&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;demo-app&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;443&lt;/span&gt;
    &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
    &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;443&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;demo-app&lt;/span&gt;
  &lt;span class="na"&gt;sessionAffinity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;None&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterIP&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;demo-app&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;demo-app&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;demo-app&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;demo-app&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;restartPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Always&lt;/span&gt;
      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-conf&lt;/span&gt;
        &lt;span class="na"&gt;configMap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-conf&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;demo-app-tls&lt;/span&gt;
        &lt;span class="na"&gt;secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;secretName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;demo-app-tls&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;demo-app&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:1.19.2-alpine&lt;/span&gt;
        &lt;span class="na"&gt;imagePullPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;IfNotPresent&lt;/span&gt;
        &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8m"&lt;/span&gt;
            &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;16Mi"&lt;/span&gt;
          &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;16m"&lt;/span&gt;
            &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;64Mi"&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;443&lt;/span&gt;
        &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-conf&lt;/span&gt;
          &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/etc/nginx/conf.d"&lt;/span&gt;
          &lt;span class="na"&gt;readOnly&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;demo-app-tls&lt;/span&gt;
          &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/run/secrets/nginx-cert"&lt;/span&gt;
          &lt;span class="na"&gt;readOnly&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now comes the TLS network encryption part, where an Ingress config &lt;code&gt;ingress.yaml&lt;/code&gt; is applied:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;networking.k8s.io/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ingress&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;demo-app&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ingress.kubernetes.io/proxy-body-size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;4m&lt;/span&gt;
    &lt;span class="na"&gt;kubernetes.io/tls-acme&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
    &lt;span class="na"&gt;kubernetes.io/ingress.class&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nginx"&lt;/span&gt;
    &lt;span class="na"&gt;nginx.ingress.kubernetes.io/backend-protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HTTPS"&lt;/span&gt;
    &lt;span class="na"&gt;nginx.ingress.kubernetes.io/proxy-ssl-secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NAMESPACE/demo-app-tls"&lt;/span&gt;
    &lt;span class="na"&gt;nginx.ingress.kubernetes.io/proxy-ssl-verify&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;YOUR-NAME.EXAMPLE-CLUSTER.com&lt;/span&gt;
      &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
            &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;serviceName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;demo-app&lt;/span&gt;
              &lt;span class="na"&gt;servicePort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;443&lt;/span&gt;
  &lt;span class="na"&gt;tls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;YOUR-NAME.EXAMPLE-CLUSTER.com&lt;/span&gt;
      &lt;span class="na"&gt;secretName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;YOUR-NAME.EXAMPLE-CLUSTER.com&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Important Note&lt;/strong&gt;: If your deployment is not within the same namespace of the Ingress controller (which is the usual case), you need to specify the namespace for &lt;code&gt;proxy-ssl-secret&lt;/code&gt;, i.e. &lt;code&gt;NAMESPACE/demo-app-tls&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Network encryption with multiple back ends
&lt;/h2&gt;

&lt;p&gt;Microservices mean having many backend apps, but there is &lt;strong&gt;only one&lt;/strong&gt; &lt;code&gt;proxy-ssl-secret&lt;/code&gt; configuration per Ingress. To serve multiple apps from the same Ingress you may configure the Ingress to treat all services with the same name, as shown in the example below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;nginx.ingress.kubernetes.io/proxy-ssl-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;demo-app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What’s done under the hood is that the proxy name got overridden as &lt;code&gt;demo-app&lt;/code&gt; for all services, so that they are served with the same certificate. This will slightly weaken the security, again weigh different options and decide what level of security you are looking to achieve. To go for a higher level of communication security, perhaps you’d like to create several Ingresses instead. Don’t hesitate and let me know if you have other ideas, it’s always nice interacting my fellow developers!&lt;/p&gt;

&lt;p&gt;You can also apply a wild card like &lt;code&gt;*.svc.cluster.local&lt;/code&gt; to match services, but by doing this technically all services are trusted which is just not very elegant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the demo server
&lt;/h2&gt;

&lt;p&gt;Below is a snippet for creating a self-signed certificate. Note that this is just a simple example and you should not copy this impetuously for production:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# root CA&lt;/span&gt;
openssl genrsa &lt;span class="nt"&gt;-out&lt;/span&gt; rootCA.key 4096
openssl req &lt;span class="nt"&gt;-x509&lt;/span&gt; &lt;span class="nt"&gt;-nodes&lt;/span&gt; &lt;span class="nt"&gt;-new&lt;/span&gt; &lt;span class="nt"&gt;-key&lt;/span&gt; rootCA.key &lt;span class="nt"&gt;-sha256&lt;/span&gt; &lt;span class="nt"&gt;-days&lt;/span&gt; 1024 &lt;span class="nt"&gt;-out&lt;/span&gt; rootCA.crt
&lt;span class="c"&gt;# generate cert for demo-app&lt;/span&gt;
openssl genrsa &lt;span class="nt"&gt;-out&lt;/span&gt; demo-app.key 4096
openssl req &lt;span class="nt"&gt;-new&lt;/span&gt; &lt;span class="nt"&gt;-sha256&lt;/span&gt; &lt;span class="nt"&gt;-key&lt;/span&gt; demo-app.key &lt;span class="nt"&gt;-out&lt;/span&gt; demo-app.csr &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-subj&lt;/span&gt; &lt;span class="s2"&gt;"/C=HK/ST=HK/L=HongKong/O=Example/OU=Org/CN=demo-app"&lt;/span&gt;
openssl x509 &lt;span class="nt"&gt;-req&lt;/span&gt; &lt;span class="nt"&gt;-in&lt;/span&gt; demo-app.csr &lt;span class="nt"&gt;-CA&lt;/span&gt; rootCA.crt &lt;span class="nt"&gt;-CAkey&lt;/span&gt; rootCA.key &lt;span class="nt"&gt;-CAcreateserial&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-out&lt;/span&gt; demo-app.crt &lt;span class="nt"&gt;-days&lt;/span&gt; 1024 &lt;span class="nt"&gt;-sha256&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you can submit your secrets to k8s:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; NAMESPACE create secret generic demo-app-tls &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--from-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tls.crt&lt;span class="o"&gt;=&lt;/span&gt;demo-app.crt &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--from-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tls.key&lt;span class="o"&gt;=&lt;/span&gt;demo-app.key &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--from-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ca.crt&lt;span class="o"&gt;=&lt;/span&gt;rootCA.crt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here the actual deployment and ingress are applied:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; NAMESPACE apply &lt;span class="nt"&gt;-f&lt;/span&gt; backend.yaml
kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; NAMESPACE apply &lt;span class="nt"&gt;-f&lt;/span&gt; ingress.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait for the deployment to take effect, the server will be ready on &lt;code&gt;https://YOUR-NAME.EXAMPLE-CLUSTER.com&lt;/code&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  Clean up the namespace
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; NAMESPACE delete &lt;span class="nt"&gt;-f&lt;/span&gt; ingress.yaml
kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; NAMESPACE delete &lt;span class="nt"&gt;-f&lt;/span&gt; backend.yaml
kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; NAMESPACE delete secret demo-app-tls YOUR-NAME.EXAMPLE-CLUSTER.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To learn more about working with the Ingress controller, check out these references on Kubernetes’ user guide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#backend-protocol" rel="noopener noreferrer"&gt;https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#backend-protocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#backend-certificate-authentication" rel="noopener noreferrer"&gt;https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#backend-certificate-authentication&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kubernetes</category>
      <category>security</category>
      <category>devops</category>
    </item>
    <item>
      <title>How to Build an AI Text Generator: Text Generation with a GPT-2 Model</title>
      <dc:creator>Elliot Wong</dc:creator>
      <pubDate>Wed, 03 Feb 2021 06:34:06 +0000</pubDate>
      <link>https://dev.to/oursky/how-to-build-an-ai-text-generator-text-generation-with-a-gpt-2-model-4346</link>
      <guid>https://dev.to/oursky/how-to-build-an-ai-text-generator-text-generation-with-a-gpt-2-model-4346</guid>
      <description>&lt;p&gt;&lt;em&gt;We wrote this after the &lt;a href="https://oursky.com" rel="noopener noreferrer"&gt;Oursky&lt;/a&gt; &lt;a href="https://skylab.ai" rel="noopener noreferrer"&gt;Skylab.ai&lt;/a&gt; team completed an AI content generator for a startup client, and we’d like to share our experience and journey._ From a corpus of stories with an aligned writing style, provided by our client, we trained a text generation model that outputs similar text pieces.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In this technical report, we will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Go through what a language model is.&lt;/li&gt;
&lt;li&gt; Discuss how to use language modeling to generate articles.&lt;/li&gt;
&lt;li&gt; Explain what Generative Pre-Trained Transformer 2 (GPT-2) is and how it can be used for language modeling.&lt;/li&gt;
&lt;li&gt; Visualize text predictions – print out our GPT-2 model’s internal states where input words affect the next’s prediction the most.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Demo with a Generic GPT-2
&lt;/h2&gt;

&lt;p&gt;Let's start with a GIF showing the outputs from a standard GPT2 model, when it was fed with 1. a sentence randomly extracted from a Sherlock Holmes book, 2. the definition of Software Engineering on Wikipedia.&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%2Fcode.oursky.com%2Fwp-content%2Fuploads%2F2021%2F02%2F123_3.gif" 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%2Fcode.oursky.com%2Fwp-content%2Fuploads%2F2021%2F02%2F123_3.gif" alt="GPT-2 Demo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  Basic knowledge on Natural Language Processing with python&lt;/li&gt;
&lt;li&gt;  Understandings on &lt;a href="https://en.wikipedia.org/wiki/Probability_theory" rel="noopener noreferrer"&gt;Probability Theory&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before we start building a predictive text generator, let’s go through a few concepts first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Language Model
&lt;/h2&gt;

&lt;p&gt;A language model is just a &lt;strong&gt;probability distribution of a sequence of words&lt;/strong&gt;. For example, given a language model of English, we can ask the probability of seeing a sentence, &lt;em&gt;“All roads lead to Rome”&lt;/em&gt; in English.  &lt;/p&gt;

&lt;p&gt;We could also estimate that the probability of seeing grammatically wrong or nonsensical sentences – &lt;em&gt;“jump hamburger I”&lt;/em&gt; definitely has a much lower probability of being correct than &lt;em&gt;“I eat hamburger”&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Let’s pull in some mathematical notations to describe a language model better.&lt;/p&gt;

&lt;p&gt;P(w1, w2, …, wn) means the probability of having the sentence &lt;em&gt;“w1 w2 … wn”&lt;/em&gt;. Here, the language model is a probability distribution instead of a probability. Having a probability distribution means we can tell the value of P(All, roads, lead, to, Rome) or P(I, eat, hamburger) if we know any wi=1…n for any n in P(w1, w2, …, wn).  &lt;/p&gt;

&lt;p&gt;A bit more on the notation first. Whenever you see P(hello, world), where items inside P() are actual words, P() is then describing a probability since wi=1…n and n are known (the former = &lt;em&gt;“hello”&lt;/em&gt;, &lt;em&gt;“world”&lt;/em&gt; while the latter = 2). If items inside P() are unknown, P() is then indicating a probability distribution. From here on out, we’ll use &lt;em&gt;“probability”&lt;/em&gt; and &lt;em&gt;“probability distribution”&lt;/em&gt; interchangeably used unless specified.  &lt;/p&gt;

&lt;p&gt;Sometimes, it’s more convenient if we express P(w1, w2, …, wn) as P(w, context). What happens here is that we lump w1 to wn-1 (i.e., all words of a sentence except the last one) to a bulky stuff that we call “context”. We can then calculate the chance of being in this “context” (seeing previous n-1 words) and ending up with the word &lt;em&gt;“w”&lt;/em&gt; at the end.&lt;/p&gt;

&lt;p&gt;Here, P(w1, w2, …, wn) and P(w, context) are describing the same thing.&lt;/p&gt;

&lt;p&gt;Using the &lt;a href="https://en.wikipedia.org/wiki/Chain_rule_(probability)" rel="noopener noreferrer"&gt;chain rule&lt;/a&gt;, we could write P(w, context) as P(w | context) P(context). We’d like to do this because P(w | context) is, in fact, the target we want most of the time. P(w | context) here is a conditional probability distribution. It tells the chance of seeing a word w given that the context (i.e. previous words) is known.&lt;/p&gt;

&lt;p&gt;Now let’s put in some words to P(w | context), say, P(apple | context) or P(orange | context). Assuming we have the previous words, we can start predicting how likely it is to have &lt;em&gt;“apple”&lt;/em&gt; or &lt;em&gt;“orange”&lt;/em&gt; as the next word of this sentence. By obtaining the &lt;em&gt;“mostly likely next word”&lt;/em&gt;, we can start creating some article generation AI models.  &lt;/p&gt;

&lt;p&gt;Right, so now we need a language model. How do we get one? &lt;a href="https://towardsdatascience.com/learning-nlp-language-models-with-real-data-cdff04c51c25" rel="noopener noreferrer"&gt;Another article&lt;/a&gt; answers this question.&lt;/p&gt;

&lt;p&gt;One approach is to count the number of wn that comes after w1 to wn-1 on a large text corpus, which will build a n-gram language model. Another is to directly learn the language model using a neural network by feeding lots of text.&lt;/p&gt;

&lt;p&gt;In our case, we used the latter approach by using the GPT-2 model to learn the language model.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Text Generation with a Language Model&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;As mentioned, P(w | context) is the basis for a neural network text generator.  &lt;/p&gt;

&lt;p&gt;P(w | context) tells the probability distribution of all English words given all seen words (as context). For example, for P(w | &lt;em&gt;“I eat”&lt;/em&gt;), we would expect a higher probability when w is a noun rather than a verb. The likelihood of w being a food is much higher than other nouns like &lt;em&gt;“book”&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;To generate the next word with all seen words, we could keep adding one word at a time with P(w | context) until we have enough for a sentence or have reached some ending word/character like a full stop.  &lt;/p&gt;

&lt;p&gt;There are various approaches on how to pick the next word, which we discuss below.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Greedy Approach&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;One approach is to pick the word with the highest probability. A quick example would be:&lt;/p&gt;

&lt;p&gt;P(w | &lt;em&gt;“I eat”&lt;/em&gt;), where &lt;em&gt;“hamburger”&lt;/em&gt; has the highest probability of being w among all words from a dictionary. We call this the greedy approach for sentence generation.  &lt;/p&gt;

&lt;p&gt;This approach is quick and very simple. The main drawback is that for the same set of previous words, we will always generate the same sentence. In other words, it lacks creativity.  &lt;/p&gt;

&lt;p&gt;Plus, when we always pick the highest probability, so it’s very easy to fall in the case of degenerate repetition, like getting the same chunk of text during sentence generation. For example:&lt;/p&gt;

&lt;pre&gt;I eat hamburger for breakfast. I eat hamburger for breakfast. I eat hamburger for breakfast ...&lt;/pre&gt;

&lt;p&gt;Not so human-like, right? We need something more random to create a language generator that yields human readable sentences.&lt;/p&gt;

&lt;h3&gt;
  
  
  Beam Approach
&lt;/h3&gt;

&lt;p&gt;Another approach is to generate lots of sentences first, then pick the most likely sentence.  &lt;/p&gt;

&lt;p&gt;Let’s assume that there are 20,000 words in the dictionary, and we want to generate a sentence with 5 words starting with word &lt;em&gt;“I”&lt;/em&gt;. The number of all possible sentences that we could generate will be 20000&lt;sup&gt;4&lt;/sup&gt;, or one hundred and sixty quadrillion. Clearly, that’s too many! We cannot calculate all those sentences’ probability within a reasonable time, even with a powerful computer.  &lt;/p&gt;

&lt;p&gt;Instead of constructing all possible sentences, we could instead just track the top-N partial sentences. At the end, we only need to check the probability of N sentences. By doing so, we hope to search the top-N likeliest sentence without having to try all combinations. This kind of searching is called beam search, and N is the beam width.  &lt;/p&gt;

&lt;p&gt;The decision tree figure below illustrates a case of generating a sentence with three words, starting with &lt;em&gt;“I”&lt;/em&gt; with N = 2. This means we only track top-2 partial sentences.&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%2Fcode.oursky.com%2Fwp-content%2Fuploads%2F2020%2F05%2FArticle-Generation-and-GPT-2-Model-1-1160x284.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%2Fcode.oursky.com%2Fwp-content%2Fuploads%2F2020%2F05%2FArticle-Generation-and-GPT-2-Model-1-1160x284.png" alt="Decision Tree on Text Predictions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here, we first check P(w | &lt;em&gt;“I”&lt;/em&gt;). Among all the possible words, the language model tells &lt;em&gt;“eat”&lt;/em&gt; and &lt;em&gt;“read”&lt;/em&gt; are the most probable next words. Hence, in the next step, we’ll only consider the trees of P(w | &lt;em&gt;“I eat”&lt;/em&gt;) and P(w | &lt;em&gt;“I read”&lt;/em&gt;) and ignore other possibilities like sentences that start with  &lt;em&gt;“I drink”&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Afterwards, we repeat the same procedure and find the two most probable words after &lt;em&gt;“I eat”&lt;/em&gt; or &lt;em&gt;“I read”&lt;/em&gt;. Among all that start with &lt;em&gt;“I eat”&lt;/em&gt; and &lt;em&gt;“I read”&lt;/em&gt;, P(&lt;em&gt;“hamburger”&lt;/em&gt; | &lt;em&gt;“I eat”&lt;/em&gt;) and P(&lt;em&gt;“cake”&lt;/em&gt; | &lt;em&gt;“I eat”&lt;/em&gt;) have the highest two probabilities. We’ll thus only expand the search with sentence prefixes &lt;em&gt;“I eat hamburger”&lt;/em&gt; and &lt;em&gt;“I eat cake”&lt;/em&gt; while the &lt;em&gt;“I read”&lt;/em&gt; branch dies.&lt;/p&gt;

&lt;p&gt;We will keep repeating the &lt;em&gt;“expand and pick best-N”&lt;/em&gt; procedure until we have a sentence with desired length. This’ll finally return a sentence with the highest probability.&lt;/p&gt;

&lt;p&gt;You may already notice that when the beam width is reduced to 1, the beam search will become the greedy approach. When the beam width equals the size of the dictionary, beam search becomes an exhaustive search. Beam search allows us to choose between sentence quality and speed.&lt;/p&gt;

&lt;p&gt;With beam width larger than 1, beam search tends to generate more promising sentences. However, like the greedy approach, the lack of randomness remains. The same sentence prefix will lead to the same sentence, and degenerate repetition will likely to happen.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pure Sampling
&lt;/h3&gt;

&lt;p&gt;The drawbacks of beam search and greedy approaches are due to the fact that we’re picking the most probable choice. Instead of picking the most probable word from P(w | context), we could sample a word with P(w | context). Time to add some randomness!&lt;/p&gt;

&lt;p&gt;For example, with a sentence that start with &lt;em&gt;“I”&lt;/em&gt;, we can sample a word according to P(w | &lt;em&gt;“I”&lt;/em&gt;), as the sampling is random. Even if P(&lt;em&gt;“eat”&lt;/em&gt; | &lt;em&gt;“I”&lt;/em&gt;) &amp;gt; P(&lt;em&gt;“read”&lt;/em&gt; | &lt;em&gt;“I”&lt;/em&gt;), we could still sample the word &lt;em&gt;“read”&lt;/em&gt;. Using sampling, we’ll have a very high chance of getting a new sentence in each generation.&lt;/p&gt;

&lt;p&gt;Sentence generated from pure sampling will be free from degenerate repetition, but it tends to result in some gibberish.&lt;/p&gt;

&lt;h3&gt;
  
  
  Top-k Sampling and Sampling with Temperature
&lt;/h3&gt;

&lt;p&gt;There are common ways to improve pure sampling.&lt;/p&gt;

&lt;p&gt;There’s Top-k sampling. Instead of sampling from full P(w | context), we only sample from top K words according to P(w | context).&lt;/p&gt;

&lt;p&gt;Another is sampling with temperature. It means we reshape the P(w | context) with a temperature factor &lt;strong&gt;t&lt;/strong&gt;, where &lt;strong&gt;t&lt;/strong&gt; is between 0 and 1.  &lt;/p&gt;

&lt;p&gt;This is where we’re using a neural network to estimate a language model. Instead of probability values (which are in the range of 0 to 1), we are getting a real number that could be in any range, which is called logits. We can convert logits to probability value using the &lt;a href="https://en.wikipedia.org/wiki/Softmax_function" rel="noopener noreferrer"&gt;softmax function&lt;/a&gt;.  &lt;/p&gt;

&lt;p&gt;Temperature &lt;strong&gt;t&lt;/strong&gt; is a part in applying the softmax function to retrieve the probabilities. This is to reshape the resultant P(w | context) by dividing each logit value by &lt;strong&gt;t&lt;/strong&gt; before applying the softmax function. As &lt;strong&gt;t&lt;/strong&gt; is between 0 and 1, dividing it will amplify the logit value.&lt;/p&gt;

&lt;p&gt;Summing up, more probable words become even more probable while the less probable ones become even less probable. Top-k sampling and sampling with temperature usually are applied together.&lt;/p&gt;

&lt;h3&gt;
  
  
  Nucleus Sampling
&lt;/h3&gt;

&lt;p&gt;When using Top-k sampling, we need to decide which k to use. The best k varies depending on context. The idea of Top-k sampling is to ignore very unlikely words according to P(w | context).&lt;/p&gt;

&lt;p&gt;We can do this in another way. Instead of focusing on Top-k words in sampling, we filter out words whose sum of probabilities is less than a certain threshold, and we only sample from the remaining words.  &lt;/p&gt;

&lt;p&gt;This approach is called nucleus sampling. According to &lt;a href="https://arxiv.org/abs/1904.09751" rel="noopener noreferrer"&gt;The Curious Case of Neural Text Degeneration&lt;/a&gt;, the original paper that proposed nucleus sampling, we should choose p = 0.95, which implies that the threshold value is 1-p = 0.05. By doing nucleus sampling with p = 0.95, we could generate text pieces that are statistically most similar to human-written text.  &lt;/p&gt;

&lt;p&gt;The paper is a must-read! It provides a lot of comparison among human-written text and texts generated through various approaches (beam search, top-k sampling, nucleus sampling, etc.), measured by different metrics.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction to GPT-2 Model
&lt;/h2&gt;

&lt;p&gt;Time to dive into the AI model!&lt;/p&gt;

&lt;p&gt;Like we mentioned, we used a neural network, &lt;a href="https://openai.com/blog/better-language-models/" rel="noopener noreferrer"&gt;GPT-2&lt;/a&gt; model from &lt;a href="https://openai.com/" rel="noopener noreferrer"&gt;OpenAI&lt;/a&gt;, to estimate the language model.  &lt;/p&gt;

&lt;p&gt;GPT-2 is a &lt;a href="https://arxiv.org/abs/1706.03762" rel="noopener noreferrer"&gt;Transformer&lt;/a&gt;-based model trained for language modelling. It can be fine-tuned to solve a diverse amount of natural language processing (NLP) problems such as text generation, summarization, question answering, translation, and sentiment analysis, among others.&lt;/p&gt;

&lt;p&gt;Throughout this article some NLP Python code snippets will be provided to aid reading.&lt;/p&gt;

&lt;p&gt;Diving into the GPT-2 model itself deserves a separate blog. Here, we’ll focus on a few main concepts. We highly recommend reading two awesome articles from Jay Alammar on &lt;a href="http://jalammar.github.io/illustrated-transformer/" rel="noopener noreferrer"&gt;Transformer&lt;/a&gt; and &lt;a href="http://jalammar.github.io/illustrated-gpt2/" rel="noopener noreferrer"&gt;GPT-2&lt;/a&gt; for more in-depth information.  &lt;/p&gt;

&lt;p&gt;Here, we’ll talk about how GPT-2 model works by building it piece by piece.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example of GPT 2 – Input and Output
&lt;/h3&gt;

&lt;p&gt;First, let’s describe the input and output of the GPT-2 model. We’ll start small and seek to construct a sentence first.&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%2Fcode.oursky.com%2Fwp-content%2Fuploads%2F2020%2F05%2FArticle-Generation-and-GPT-2-Model-2.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%2Fcode.oursky.com%2Fwp-content%2Fuploads%2F2020%2F05%2FArticle-Generation-and-GPT-2-Model-2.png" alt="Text Generation with GPT-2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Given words in its &lt;a href="https://en.wikipedia.org/wiki/Word_embedding" rel="noopener noreferrer"&gt;embedded form&lt;/a&gt;, GPT-2 transforms the input word-embedding vector (blue ellipses) to the output word embedding (purple ellipses). This transformation would not change the dimension of the word embedding (although it could). Output word embedding is known as the hidden state.  &lt;/p&gt;

&lt;p&gt;During the transformation, input embeddings from previous words will affect the result of the current word’s output embedding, but not the other way round. In our example, the output embedding of &lt;em&gt;“cake”&lt;/em&gt; will depend on the input embedding of &lt;em&gt;“I”&lt;/em&gt;, &lt;em&gt;“eat”&lt;/em&gt;, and &lt;em&gt;“cake”&lt;/em&gt;. On the other hand, the output embedding of &lt;em&gt;“I”&lt;/em&gt; (the first word) will only depend on the input embedding of &lt;em&gt;“I”&lt;/em&gt;.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Due to this, the output embedding of the last input word somehow captures the essence of the whole input sentence.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To obtain the language model, we could have a matrix &lt;strong&gt;WLM **whose number of column equals the dimension of output embedding. **WLM&lt;/strong&gt; has a number of rows that equals the dictionary size and bias vector **bLM **with its dimension being the dictionary size.  &lt;/p&gt;

&lt;p&gt;We can then compute the logit of each word in the dictionary by multiplying &lt;strong&gt;WLM&lt;/strong&gt; with the output embedding of the last word, then adding &lt;strong&gt;bLM&lt;/strong&gt;. To convert those logits to probabilities, we’ll apply the softmax function, and its result could be interpreted as P(w | context).&lt;/p&gt;

&lt;h3&gt;
  
  
  Inside the GPT-2 Model
&lt;/h3&gt;

&lt;p&gt;Until now, we’ve discussed how output word embeddings are computed from input word embeddings.  &lt;/p&gt;

&lt;p&gt;Input word embeddings are simply vectors. The first steps of the transformation is to create even more vectors from those input word embeddings. Three vectors, namely, the &lt;strong&gt;key vector&lt;/strong&gt;, &lt;strong&gt;query vector&lt;/strong&gt; and &lt;strong&gt;value vector&lt;/strong&gt;, will be created based on each input word embedding.  &lt;/p&gt;

&lt;p&gt;Producing these vectors is simple. We just need three matrices &lt;strong&gt;Wkey&lt;/strong&gt;, &lt;strong&gt;Wquery,&lt;/strong&gt; and &lt;strong&gt;Wvalue. **By multiplying the input word embedding with these three matrices, we’ll get the corresponding key, query, and value vector of the corresponding input word. **Wkey&lt;/strong&gt;, **Wquery **and **Wvalue **are parts of the parameters of the GPT-2 model.  &lt;/p&gt;

&lt;p&gt;To further demonstrate, let’s consider &lt;strong&gt;I&lt;/strong&gt;&lt;strong&gt;input&lt;/strong&gt; , the input word embedding of &lt;em&gt;“I”&lt;/em&gt;. Here, we have:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ikey = Wkey Iinput, Iquery = Wquery Iinput, Ivalue = Wvalue Iinput&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We’ll use the same &lt;strong&gt;Wkey&lt;/strong&gt;, &lt;strong&gt;Wquery **and **Wvalue&lt;/strong&gt; to compute the key, query, and value vectors for all other words.  &lt;/p&gt;

&lt;p&gt;After we know how to compute the key, query, and value vectors for each input word, it’s time to use these vectors to compute the output word embedding.  &lt;/p&gt;

&lt;p&gt;As mentioned, the current word’s output embedding will depend on the current word’s input embedding and all the previous words’ input embedding.  &lt;/p&gt;

&lt;p&gt;The output embedding of a current word is the weighted sum of the current word and all its previous words’ value vectors. This also explains why value vectors are called as such.  &lt;/p&gt;

&lt;p&gt;Let’s take *&lt;em&gt;eatoutput **as the output embedding of *“eat”&lt;/em&gt;. Its value is computed by:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;eat output = &lt;sup&gt;I&lt;/sup&gt; A eat I value + &lt;sup&gt;eat&lt;/sup&gt; A eat eat value&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here, &lt;strong&gt;&lt;sup&gt;I&lt;/sup&gt; A eat **and &lt;sup&gt;&lt;/sup&gt;&lt;/strong&gt;eat*&lt;em&gt; **A&lt;/em&gt;* &lt;strong&gt;eat&lt;/strong&gt; are attention values. They could be interpreted as how much attention should &lt;em&gt;“eat”&lt;/em&gt; pay on &lt;em&gt;“I”&lt;/em&gt; and &lt;em&gt;“eat”&lt;/em&gt; when computing its output embedding. To avoid shrinking the output embedding, the sum of attention values need to be to 1.  &lt;/p&gt;

&lt;p&gt;This implies that for the first word, its output embedding will be equal to its value vector; for example, &lt;strong&gt;Ioutput&lt;/strong&gt; equals to &lt;strong&gt;I&lt;/strong&gt; &lt;strong&gt;value&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;Each attention value &lt;strong&gt;&lt;sup&gt;x&lt;/sup&gt;Ay&lt;/strong&gt; is computed by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Taking the dot product between the key vector of x and query vector of y&lt;/li&gt;
&lt;li&gt;  Scaling down the dot product with the square root of the dimension of the key vector&lt;/li&gt;
&lt;li&gt;  Taking the softmax to ensure the related attention values are summing up to 1, as shown below:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;sup&gt;x&lt;/sup&gt;Ay **= softmax(&lt;/strong&gt;xkey&lt;sup&gt;T&lt;/sup&gt; yquery** / sqrt(k)), where k is the dimension of key vector.&lt;/p&gt;

&lt;p&gt;Let’s recap!&lt;/p&gt;

&lt;p&gt;We should now know how output embedding is computed as the weighted sum of value vectors of the current and previous words. The weights used in the sum are called attention value, which is a value between two words, and is computed by taking the dot product of key vector of one word and query vector of another word. As the weights should sum up to 1, we’ll also take the softmax on the dot product.&lt;/p&gt;

&lt;h3&gt;
  
  
  Structure Replication
&lt;/h3&gt;

&lt;p&gt;What we’ve discussed so far is just the &lt;strong&gt;attention layer&lt;/strong&gt; in GPT-2. This layer covers most of the details, as the rest of the GPT-2 model structure is just a replication of the attention layer.&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%2Fcode.oursky.com%2Fwp-content%2Fuploads%2F2020%2F05%2FArticle-Generation-and-GPT-2-Model-3.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%2Fcode.oursky.com%2Fwp-content%2Fuploads%2F2020%2F05%2FArticle-Generation-and-GPT-2-Model-3.png" alt="Attention Layer in GPT-2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s continue our GPT-2 model construction journey. GPT-2 uses multiple attention layers. This is the so-called multi-head attention.  &lt;/p&gt;

&lt;p&gt;While those attention layers run in parallel, they’re not dependent on each other and don’t share weights, i.e., there will be a different set of &lt;strong&gt;Wkey&lt;/strong&gt;, &lt;strong&gt;Wquery, **and **Wvalue&lt;/strong&gt; for each attention layer.  &lt;/p&gt;

&lt;p&gt;As we have multiple attention layers, we’ll have multiple output word embeddings for each word. To combine all those output word embeddings into one, we’ll first concatenate all the output word embeddings from different attention layers. We then multiply the concatenated matrix &lt;strong&gt;W&lt;/strong&gt;&lt;strong&gt;project&lt;/strong&gt; to make the output word embedding have the same dimension as the input word embedding.&lt;/p&gt;

&lt;p&gt;The output word embeddings we got so far is actually not the final one. The output word embeddings will further go through a &lt;a href="https://en.wikipedia.org/wiki/Feedforward_neural_network" rel="noopener noreferrer"&gt;feedforward layer&lt;/a&gt; and transform into actual output word embeddings.  &lt;/p&gt;

&lt;p&gt;These attention layers running in parallel together with the feedforward layer are grouped to a block called the &lt;strong&gt;decoder block&lt;/strong&gt;&lt;sup&gt;1&lt;/sup&gt;.&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%2Fcode.oursky.com%2Fwp-content%2Fuploads%2F2020%2F05%2FArticle-Generation-and-GPT-2-Model-4.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%2Fcode.oursky.com%2Fwp-content%2Fuploads%2F2020%2F05%2FArticle-Generation-and-GPT-2-Model-4.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GPT-2 doesn’t just include one decoder block. There’s a chain of it. We choose the input word embedding and output word embedding to have the same dimensionality so that we could chain the decoder blocks.&lt;/p&gt;

&lt;p&gt;These decoder blocks have exactly the same structure but don’t share weight.&lt;/p&gt;

&lt;p&gt;The GPT-2 model has a different sizes. They are different in the embedding dimensionality, key, query, value vector’s dimensionality, number of attention layer in each decoder block, and number of decoder blocks in the model.&lt;/p&gt;

&lt;h4&gt;
  
  
  Some Omitted Details
&lt;/h4&gt;

&lt;p&gt;Here are some details worth noting, and you can take these as pointers to learn more about them:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; GPT-2 uses &lt;a href="https://en.wikipedia.org/wiki/Byte_pair_encoding" rel="noopener noreferrer"&gt;Byte pair encoding&lt;/a&gt; when tokenizing the input string. One token does not necessarily correspond to one word. GPT-2 works in terms of tokens instead of words.&lt;/li&gt;
&lt;li&gt; Positional embeddings are added to the input embeddings of the first decoder block so as to encode the word order information in the word embedding.&lt;/li&gt;
&lt;li&gt; All residual addition and normalization layers are omitted.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Training the GPT-2 Model
&lt;/h3&gt;

&lt;p&gt;So, now you have a sense of how GPT-2 works. You know how GPT-2 can be used to estimate the language model by converting last word’s output embedding to logits using &lt;strong&gt;WLM&lt;/strong&gt; and &lt;strong&gt;bLM&lt;/strong&gt;, then to probabilities.&lt;/p&gt;

&lt;p&gt;We can now talk about training the GPT-2 model for text generation.  &lt;/p&gt;

&lt;p&gt;The first step to train a GPT-2 text generator is language model estimation. Given an input string, such as &lt;em&gt;“I eat cake”&lt;/em&gt;, GPT-2 can estimate P(eat | &lt;em&gt;“I”&lt;/em&gt;) and P(cake | &lt;em&gt;“I eat”&lt;/em&gt;).  &lt;/p&gt;

&lt;p&gt;For this input string in training, we’ll assume the following:&lt;br&gt;&lt;br&gt;
P(eat | &lt;em&gt;“I”&lt;/em&gt;) = 1, P(w != eat | &lt;em&gt;“I”&lt;/em&gt;) = 0  &lt;/p&gt;

&lt;p&gt;P(cake | &lt;em&gt;“I eat”&lt;/em&gt;) = 1, P(w != cake | &lt;em&gt;“I eat”&lt;/em&gt;) = 0  &lt;/p&gt;

&lt;p&gt;Now that we have estimated and targeted the probability distributions, we can then compute the &lt;a href="https://en.wikipedia.org/wiki/Cross_entropy" rel="noopener noreferrer"&gt;cross entropy&lt;/a&gt; loss, and use this to update the weights.  &lt;/p&gt;

&lt;p&gt;As you’ll see, we need to feed it with a large amount of text to train the GPT-2 model.&lt;/p&gt;
&lt;h3&gt;
  
  
  Testing and Fine-Tuning GPT-2
&lt;/h3&gt;

&lt;p&gt;To quickly test GPT-2 on article generation, we’ll use &lt;a href="https://huggingface.co/transformers/" rel="noopener noreferrer"&gt;Huggingface 🤗 Transformers&lt;/a&gt;. It is a Python library for developers to quickly test pre-trained and transformers-based NLP models. GPT-2 is one of them, along with others like PyTorch and TensorFlow.  &lt;/p&gt;

&lt;p&gt;To fine-tune a pre-trained model, we could use the &lt;a href="https://github.com/huggingface/transformers/blob/master/examples/legacy/run_language_modeling.py" rel="noopener noreferrer"&gt;run_langauge_modeling.py&lt;/a&gt;. All we need are two text files; one containing the training text pieces, and another containing the text pieces for evaluation.  &lt;/p&gt;

&lt;p&gt;Here’s an example of using run_language_modeling.py for fine-tuning a pre-trained model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python run_language_modeling.py &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--output_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;output &lt;span class="se"&gt;\ &lt;/span&gt;         &lt;span class="c"&gt;# The trained model will be store at ./output&lt;/span&gt;
    &lt;span class="nt"&gt;--model_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gpt2 &lt;span class="se"&gt;\ &lt;/span&gt;           &lt;span class="c"&gt;# Tell huggingface transformers we want to train gpt-2&lt;/span&gt;
    &lt;span class="nt"&gt;--model_name_or_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gpt2 &lt;span class="se"&gt;\ &lt;/span&gt;   &lt;span class="c"&gt;# This will use the pre-trained gpt2 samll model&lt;/span&gt;
    &lt;span class="nt"&gt;--do_train&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--train_data_file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$TRAIN_FILE&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--do_eval&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--eval_data_file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$TEST_FILE&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--per_gpu_train_batch_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1   &lt;span class="c"&gt;# For GPU training only, you may increase it if your GPU has more memory to hold more training data.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Huggingface 🤗 Transformers has a lot of built-in functions, and generating text is one of them.&lt;/p&gt;

&lt;p&gt;The following is a code snippet of text generation using a pre-trained GPT-2 model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from transformers import (
    GPT2LMHeadModel,
    GPT2Tokenizer,
)

tokenizer = GPT2Tokenizer.from_pretrained("gpt2")
model = GPT2LMHeadModel.from_pretrained("gpt2")

sentence_prefix = "I eat"

input_ids = tokenizer.encode(
    sentence_prefix,
    add_special_tokens=False,
    return_tensors="pt",
    add_space_before_punct_symbol=True
)

output_ids = model.generate(
    input_ids=input_ids,
    do_sample=True,
    max_length=20,  # desired output sentence length
    pad_token_id=model.config.eos_token_id,
)[0].tolist()

generated_text = tokenizer.decode(
    output_ids,
    clean_up_tokenization_spaces=True)

print(generated_text)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Attention Visualization
&lt;/h2&gt;

&lt;p&gt;Thanks to jessevig’s &lt;a href="https://github.com/jessevig/bertviz" rel="noopener noreferrer"&gt;BertViz&lt;/a&gt; tool, we can peek at how GPT-2 works by visualizing the attention values.&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%2Fcode.oursky.com%2Fwp-content%2Fuploads%2F2020%2F05%2FArticle-Generation-and-GPT-2-Model-5-1160x950.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%2Fcode.oursky.com%2Fwp-content%2Fuploads%2F2020%2F05%2FArticle-Generation-and-GPT-2-Model-5-1160x950.png" alt="Attention Values Visualised"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The figure above is a visualization of attention values on each decoder block (from top to bottom of the grid, with the first row as the first block). Each attention head (from left to right) of the GPT-2 small model takes &lt;em&gt;“I disapprove of what you say, but”&lt;/em&gt; as input.&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%2Fcode.oursky.com%2Fwp-content%2Fuploads%2F2020%2F05%2FArticle-Generation-and-GPT-2-Model-6.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%2Fcode.oursky.com%2Fwp-content%2Fuploads%2F2020%2F05%2FArticle-Generation-and-GPT-2-Model-6.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the left is a zoomed-in look at the 2&lt;sup&gt;nd&lt;/sup&gt; block’s 6&lt;sup&gt;th&lt;/sup&gt; attention head’s result.&lt;/p&gt;

&lt;p&gt;The words on the left are the output, and those on the right are the input. The opacity of the line indicates how much attention the output word paid to the input words.&lt;/p&gt;

&lt;p&gt;An interesting tidbit here is that, most of the time, the first word is paid the most attention. This general pattern remains even if we use other input sentences.&lt;/p&gt;

&lt;h2&gt;
  
  
  Word Importance Visualization
&lt;/h2&gt;

&lt;p&gt;Purely looking at the attention values doesn’t seem to give us clues on how the input sentence affects how the GPT-2 model picks its next word. One of the reasons could be that it’s hard to imagine how the attention is utilized for the next word text prediction, as there’s still a &lt;strong&gt;Wproject&lt;/strong&gt; feedforward layer to transform the attention layer’s output.  &lt;/p&gt;

&lt;p&gt;So, we’re interested in how the input sentence affects the probability distribution of the next word. We want to know which word in the input sentence will affect the next word’s probability distribution the most.&lt;/p&gt;

&lt;h3&gt;
  
  
  Measure Word Importance Through Input Perturbation
&lt;/h3&gt;

&lt;p&gt;In &lt;a href="http://proceedings.mlr.press/v97/guan19a.html" rel="noopener noreferrer"&gt;Towards a Deep and Unified Understanding of Deep Neural Models in NLP&lt;/a&gt;, the authors propose a way to answer this. They also provide the &lt;a href="https://github.com/icml2019paper2428/Towards-A-Deep-and-Unified-Understanding-of-Deep-Neural-Models-in-NLP" rel="noopener noreferrer"&gt;code&lt;/a&gt; that we could use to analyze the GPT-2 model with.  &lt;/p&gt;

&lt;p&gt;The paper also discussed measuring the importance of input word. The idea is to assign a value σi to each input word, where σi is initially a random value between 0 and 1.  &lt;/p&gt;

&lt;p&gt;Later on, we’ll generate some noise vector with the size of input word embedding. This noise vector will be added to the input word embedding with the weight specified in σi. This means σi tells how much noise is added to the corresponding input word.  &lt;/p&gt;

&lt;p&gt;With the original and perturbed input word embeddings, we feed both of them to our GPT-2 model and get two sets of logit from the last output embeddings.&lt;/p&gt;

&lt;p&gt;We then measure the difference (using L&lt;sup&gt;2&lt;/sup&gt; norm) between these two logits. This difference tells us how severely the perturbation is affecting the resultant logits that we use to construct the language model. We then optimize σi to minimize the difference between two logits.  &lt;/p&gt;

&lt;p&gt;We keep generating new noise vector and add them to the original input word embedding using the &lt;strong&gt;updated&lt;/strong&gt; σi. We then compute the difference between the resultant logits, and use this difference to guide the update of σi.  &lt;/p&gt;

&lt;p&gt;During the iteration, we’ll track the best σi that leads to the smallest difference in the resultant logits, and report it as the result after we reach the maximum number of iteration.  &lt;/p&gt;

&lt;p&gt;The reported σi tells us how much noise the corresponding input word could withstand in a way that will not lead to significant change in the resultant logits.  &lt;/p&gt;

&lt;p&gt;If a word is important to the resultant logits, we’d expect that the small perturbation on that word’s input embedding will lead to a significant change in the logits. Hence, the reported σi is inversely proportional to the importance of the words. The smaller the the reported σi, the more important the corresponding input word is.&lt;/p&gt;

&lt;h4&gt;
  
  
  Code Snippet
&lt;/h4&gt;

&lt;p&gt;Here’s a code snippet for visualizing the word importance. Interpreter.py could be found &lt;a href="https://github.com/icml2019paper2428/Towards-A-Deep-and-Unified-Understanding-of-Deep-Neural-Models-in-NLP/blob/master/Interpreter.py" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import torch
from transformers import GPT2Tokenizer, GPT2LMHeadModel
from Interpreter import Interpreter 

def Phi(x):
    global model
    result = model(inputs_embeds=x)[0]
    return result # return the logit of last word

model_path = "gpt2"
model = GPT2LMHeadModel.from_pretrained(model_path, output_attentions=True)
tokenizer = GPT2Tokenizer.from_pretrained(model_path)

input_embedding_weight_std = (
    model.get_input_embeddings().weight.view(1,-1)
    .std().item()
)

text = "I disapprove of what you say , but"
inputs = tokenizer.encode_plus(text, return_tensors='pt', 
                               add_special_tokens=True, 
                               add_space_before_punct_symbol=True)
input_ids = inputs['input_ids']

with torch.no_grad():
    x = model.get_input_embeddings()(input_ids).squeeze()

interpreter = Interpreter(x=x, Phi=Phi, 
                          scale=10*input_embedding_weight_std,
                          words=text.split(' ')).to(model.device)

# This will take sometime.
interpreter.optimize(iteration=1000, lr=0.01, show_progress=True)
interpreter.get_sigma()
interpreter.visualize()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Below are the reported σi and its visualization. The smaller the value, the darker the color.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;array&lt;span class="o"&gt;([&lt;/span&gt;0.8752377, 1.2462736, 1.3040292, 0.55643 , 1.3775877, 1.2515365, 1.2249271, 0.311358 &lt;span class="o"&gt;]&lt;/span&gt;, &lt;span class="nv"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;float32&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fcode.oursky.com%2Fwp-content%2Fuploads%2F2020%2F05%2FArticle-Generation-and-GPT-2-Model-7.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%2Fcode.oursky.com%2Fwp-content%2Fuploads%2F2020%2F05%2FArticle-Generation-and-GPT-2-Model-7.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From the figures above, we can now know that P( w | “I disapprove of what you say, but”) will be affected by the word “but” the most, followed by “what”, then “I”.&lt;/p&gt;

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

&lt;p&gt;To sump up, we discussed what a language model is and how to utilize it to do article generation that uses different approaches to get text similar to how humans write them.&lt;/p&gt;

&lt;p&gt;We also briefly introduced the GPT-2 model and some of its internal workings. We also saw how to use Huggingface 🤗 Transformers in applying the GPT-2 model in text predictions.&lt;/p&gt;

&lt;p&gt;We’ve visualized the attention values in GPT-2 model and used the input perturbation approach to see which word/s in the input sentence would affect the next word prediction the most.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Footnote&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; The actual structure of decoder block consists of one attention layer only. What we describe here as attention layer should be called attention head. One attention layer includes multiple attention heads and the &lt;strong&gt;Wproject&lt;/strong&gt; for combining the attention heads’ output.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>machinelearning</category>
      <category>ai</category>
      <category>python</category>
    </item>
  </channel>
</rss>
