<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="4.3.3">Jekyll</generator><link href="https://kehang.github.io/feed.xml" rel="self" type="application/atom+xml"/><link href="https://kehang.github.io/" rel="alternate" type="text/html" hreflang="en"/><updated>2024-08-27T23:05:07+00:00</updated><id>https://kehang.github.io/feed.xml</id><title type="html">blank</title><subtitle>A simple, whitespace theme for academics. Based on [*folio](https://github.com/bogoli/-folio) design. </subtitle><entry><title type="html">Battle-Tested LLM Training: From Dataset to Data Iterator</title><link href="https://kehang.github.io/blog/2024/battle-tested-llm-training-basic-input-pipeline/" rel="alternate" type="text/html" title="Battle-Tested LLM Training: From Dataset to Data Iterator"/><published>2024-08-11T10:16:16+00:00</published><updated>2024-08-11T10:16:16+00:00</updated><id>https://kehang.github.io/blog/2024/battle-tested-llm-training-basic-input-pipeline</id><content type="html" xml:base="https://kehang.github.io/blog/2024/battle-tested-llm-training-basic-input-pipeline/"><![CDATA[<p>If you find an interesting dataset (often from either Huggingface or TFDS nowadays) and you’d like to use it for LLM training, this post is for you! Specifically, I’ll be explaining the process that gradually turns a Huggingface dataset to an iterator that’s ready to feed model training with batches of data. Conceptually it takes four steps.</p> <p><img src="/assets/battle_tested_llm_training/input_pipeline_data2iter.png" alt="Alt text" width="85%"/></p> <p>To make it concrete, I’ll use MaxText’s <a href="https://github.com/google/maxtext/blob/da50760ac0baf3920305a365215f6f0c5f110ad2/MaxText/input_pipeline/_hf_data_processing.py#L121">make_hf_iterator</a> as my reference code, and choose <a href="https://huggingface.co/datasets/stas/openwebtext-10k">openwebtext-10k</a> as our input dataset.</p> <h3 id="load-raw-dataset">load raw dataset</h3> <p>First, let’s load the raw <code class="language-plaintext highlighter-rouge">openwebtext-10k</code> dataset. If <code class="language-plaintext highlighter-rouge">streaming</code> is on as shown below, data files will not be downloaded. Instead, it streams the data progressively while iterating on the dataset.</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="n">datasets</span> <span class="kn">import</span> <span class="n">load_dataset</span>

<span class="n">dataset</span> <span class="o">=</span> <span class="nf">load_dataset</span><span class="p">(</span><span class="sh">"</span><span class="s">stas/openwebtext-10k</span><span class="sh">"</span><span class="p">,</span> <span class="n">split</span><span class="o">=</span><span class="sh">"</span><span class="s">train</span><span class="sh">"</span><span class="p">,</span> <span class="n">streaming</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
</code></pre></div></div> <h3 id="tokenize">tokenize</h3> <p>At this stage, we need tokenize the raw dataset’s <code class="language-plaintext highlighter-rouge">text</code> field and trim the tokenized sequence up to predefined <code class="language-plaintext highlighter-rouge">max_length</code>. Practically, we’d first create a tokenizer either from a local file or from Huggingface via <code class="language-plaintext highlighter-rouge">tokenizer_path</code>. In the example below, we use <code class="language-plaintext highlighter-rouge">t5-small</code> tokenizer, which would be fetched from Huggingface directly.</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Sets some constants
</span><span class="n">add_bos</span><span class="p">,</span> <span class="n">add_eos</span><span class="p">,</span> <span class="n">max_length</span> <span class="o">=</span> <span class="bp">True</span><span class="p">,</span> <span class="bp">True</span><span class="p">,</span> <span class="mi">512</span>
<span class="n">tokenizer_path</span> <span class="o">=</span> <span class="sh">"</span><span class="s">t5-small</span><span class="sh">"</span>
<span class="n">data_column_name</span> <span class="o">=</span> <span class="sh">"</span><span class="s">text</span><span class="sh">"</span>

<span class="c1"># Creates a tokenizer
</span><span class="n">tokenizer</span> <span class="o">=</span> <span class="n">transformers</span><span class="p">.</span><span class="n">AutoTokenizer</span><span class="p">.</span><span class="nf">from_pretrained</span><span class="p">(</span>
    <span class="n">tokenizer_path</span><span class="p">,</span>
    <span class="n">add_bos_token</span><span class="o">=</span><span class="n">add_bos</span><span class="p">,</span>
    <span class="n">add_eos_token</span><span class="o">=</span><span class="n">add_eos</span><span class="p">,</span>
    <span class="n">model_max_length</span><span class="o">=</span><span class="n">max_length</span><span class="p">,</span>
    <span class="n">legacy</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span>
<span class="p">)</span>
</code></pre></div></div> <p>Tokenization is then accomplished by running that tokenizer via dataset <code class="language-plaintext highlighter-rouge">map</code> function, <a href="https://github.com/google/maxtext/blob/ed21f6ad1bc60285958d585753dda01e1ddfa664/MaxText/input_pipeline/_input_pipeline_utils.py#L56">_input_pipeline_utils.tokenization</a>. This function applies the above tokenizer to the field <code class="language-plaintext highlighter-rouge">data_column_name</code> of each data example and truncates the tokens up to <code class="language-plaintext highlighter-rouge">max_length</code>.</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="n">maxtext.MaxText.input_pipeline</span> <span class="kn">import</span> <span class="n">_input_pipeline_utils</span>

<span class="n">dataset</span> <span class="o">=</span> <span class="n">dataset</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span>
    <span class="n">_input_pipeline_utils</span><span class="p">.</span><span class="n">tokenization</span><span class="p">,</span>
    <span class="n">batched</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
    <span class="n">fn_kwargs</span><span class="o">=</span><span class="p">{</span><span class="sh">"</span><span class="s">hf_tokenizer</span><span class="sh">"</span><span class="p">:</span> <span class="n">tokenizer</span><span class="p">,</span> <span class="sh">"</span><span class="s">max_length</span><span class="sh">"</span><span class="p">:</span> <span class="n">max_length</span> <span class="o">-</span> <span class="mi">1</span><span class="p">,</span> <span class="sh">"</span><span class="s">column_name</span><span class="sh">"</span><span class="p">:</span> <span class="n">data_column_name</span><span class="p">},</span>
<span class="p">)</span>
<span class="c1"># Post-tokenization: renaming the field where the tokens are.
</span><span class="n">dataset</span> <span class="o">=</span> <span class="n">dataset</span><span class="p">.</span><span class="nf">select_columns</span><span class="p">([</span><span class="sh">"</span><span class="s">input_ids</span><span class="sh">"</span><span class="p">]).</span><span class="nf">rename_column</span><span class="p">(</span><span class="sh">"</span><span class="s">input_ids</span><span class="sh">"</span><span class="p">,</span> <span class="n">data_column_name</span><span class="p">)</span>
</code></pre></div></div> <h3 id="transform-pack-and-shift">transform: pack and shift</h3> <p>After tokenization, data examples become token sequences of various lengths. To increase training efficiency, we try to pack as many sequences as possible into the context window (<code class="language-plaintext highlighter-rouge">max_length</code> in the code). Here we use grain’s experimental packing API <a href="https://github.com/google/grain/blob/9b984e8a2ccdd3d377cb43b17591080b44c07009/grain/_src/python/experimental/example_packing/packing.py#L152">PackAndBatchOperation</a>.</p> <p>In multi-host setting, each host (i.e., process) gets an equal share of the global batch size (say <code class="language-plaintext highlighter-rouge">512</code>), and this input pipeline code runs at host-level in parallel, thus we want host-level batch size when we batch, i.e., <code class="language-plaintext highlighter-rouge">global_batch_size // jax.process_count()</code>.</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">grain.python</span> <span class="k">as</span> <span class="n">grain</span>
<span class="c1"># Sets some constants.
</span><span class="n">global_batch_size</span> <span class="o">=</span> <span class="mi">512</span>

<span class="c1"># Adds packing transformation.
</span><span class="n">transformations</span> <span class="o">=</span> <span class="p">[]</span>
<span class="c1"># HFNormalizeFeatures makes two copies of `text` field: one is called
# `inputs` and the other `targets`.
</span><span class="n">operations</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">_input_pipeline_utils</span><span class="p">.</span><span class="nc">HFNormalizeFeatures</span><span class="p">(</span><span class="n">data_column_name</span><span class="p">))</span>
<span class="n">transformations</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span>
    <span class="n">grain</span><span class="p">.</span><span class="n">experimental</span><span class="p">.</span><span class="nc">PackAndBatchOperation</span><span class="p">(</span>
        <span class="c1"># In multi-host setting, each host (i.e., process) gets an equal share 
</span>        <span class="c1"># of the global batch size.
</span>        <span class="c1"># And this input pipeline runs at host-level in parallel, thus we want 
</span>        <span class="c1"># host-level batch size here.
</span>        <span class="n">batch_size</span><span class="o">=</span><span class="n">global_batch_size</span> <span class="o">//</span> <span class="n">jax</span><span class="p">.</span><span class="nf">process_count</span><span class="p">(),</span>
        <span class="n">length_struct</span><span class="o">=</span><span class="p">{</span><span class="sh">"</span><span class="s">inputs</span><span class="sh">"</span><span class="p">:</span> <span class="n">max_length</span><span class="p">,</span> <span class="sh">"</span><span class="s">targets</span><span class="sh">"</span><span class="p">:</span> <span class="n">max_length</span><span class="p">},</span>
    <span class="p">)</span>
<span class="p">)</span>

<span class="c1"># Post-packing: reformating tuple to flat dict style.
</span><span class="n">transformations</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">_input_pipeline_utils</span><span class="p">.</span><span class="nc">ReformatPacking</span><span class="p">())</span>
</code></pre></div></div> <p>Finally we shift the <code class="language-plaintext highlighter-rouge">inputs</code> field by 1 token to the right, to make it ready for teacher-forcing training.</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">transformations</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">_input_pipeline_utils</span><span class="p">.</span><span class="nc">ShiftData</span><span class="p">(</span><span class="n">axis</span><span class="o">=</span><span class="mi">1</span><span class="p">))</span>
</code></pre></div></div> <h3 id="sample">sample</h3> <p>Now with all the transformations done, we need to tell each host how to sample from the transformed dataset. Most common settings include number of epochs (<code class="language-plaintext highlighter-rouge">num_epochs</code>), which shard of the dataset the current host should load (<code class="language-plaintext highlighter-rouge">shard_options</code>), whether to shuffle (<code class="language-plaintext highlighter-rouge">shuffle</code>), etc.</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">sampler</span> <span class="o">=</span> <span class="n">grain</span><span class="p">.</span><span class="nc">IndexSampler</span><span class="p">(</span>
    <span class="n">num_records</span><span class="o">=</span><span class="nf">len</span><span class="p">(</span><span class="n">dataset</span><span class="p">),</span>
    <span class="n">num_epochs</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span>
    <span class="n">shard_options</span><span class="o">=</span><span class="n">grain</span><span class="p">.</span><span class="nc">ShardOptions</span><span class="p">(</span>
        <span class="n">shard_index</span><span class="o">=</span><span class="n">dataloading_host_index</span><span class="p">,</span> <span class="n">shard_count</span><span class="o">=</span><span class="n">dataloading_host_count</span><span class="p">,</span> <span class="n">drop_remainder</span><span class="o">=</span><span class="bp">False</span>
    <span class="p">),</span>
    <span class="n">shuffle</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span>
    <span class="n">seed</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
<span class="p">)</span>
</code></pre></div></div> <h3 id="put-together">put together</h3> <p>We put everything together with <code class="language-plaintext highlighter-rouge">grain.DataLoader</code> API, which takes in the raw <code class="language-plaintext highlighter-rouge">dataset</code>, training-required <code class="language-plaintext highlighter-rouge">transformations</code> and <code class="language-plaintext highlighter-rouge">sampler</code>. The returned dataloader is ready to produce batches the downstream training loop needs (<code class="language-plaintext highlighter-rouge">iter(dataloader)</code>).</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">dataloader</span> <span class="o">=</span> <span class="n">grain</span><span class="p">.</span><span class="nc">DataLoader</span><span class="p">(</span>
    <span class="n">data_source</span><span class="o">=</span><span class="n">dataset</span><span class="p">,</span>
    <span class="n">operations</span><span class="o">=</span><span class="n">transformations</span><span class="p">,</span>
    <span class="n">sampler</span><span class="o">=</span><span class="n">sampler</span><span class="p">,</span>
    <span class="n">worker_count</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span>
    <span class="n">worker_buffer_size</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span>
    <span class="n">read_options</span><span class="o">=</span><span class="n">grain</span><span class="p">.</span><span class="nc">ReadOptions</span><span class="p">(</span><span class="n">num_threads</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">prefetch_buffer_size</span><span class="o">=</span><span class="mi">128</span><span class="p">),</span>
<span class="p">)</span>

<span class="n">data_iter</span> <span class="o">=</span> <span class="nf">iter</span><span class="p">(</span><span class="n">dataloader</span><span class="p">)</span>
<span class="n">batch</span> <span class="o">=</span> <span class="nf">next</span><span class="p">(</span><span class="n">data_iter</span><span class="p">)</span>
</code></pre></div></div> <h3 id="final-words">Final words</h3> <p>Feel free to run and fork <a href="https://colab.research.google.com/drive/1MrIvDAiWcTSma3mDKwmd5F_cxVznfxmQ#scrollTo=VbMbtALpE7om">input_pipeline_data2iter.ipynb</a> if you’d like to run a complete version of input pipeline. It’s worth noting that the returned <code class="language-plaintext highlighter-rouge">batch</code> sits in host CPU memory and so it’s necessary to further shard it across TPU devices before feeding the batch to <code class="language-plaintext highlighter-rouge">pjitted</code> train step. This could be done by <a href="https://github.com/google/maxtext/blob/ead18fbe6f2d8a6cbae6bbd38568146919e20e18/MaxText/multihost_dataloading.py#L93">MultiHostDataLoadIterator</a>. If you’d like to know the details, <a href="/blog/2024/battle-tested-llm-training-multihost-input-pipeline/">this previous post</a> could be of interest. If you’d like to run the input pipeline</p>]]></content><author><name></name></author><category term="llm"/><summary type="html"><![CDATA[If you find an interesting dataset (often from either Huggingface or TFDS nowadays) and you’d like to use it for LLM training, this post is for you! Specifically, I’ll be explaining the process that gradually turns a Huggingface dataset to an iterator that’s ready to feed model training with batches of data. Conceptually it takes four steps.]]></summary></entry><entry><title type="html">Battle-Tested LLM Training: Multi-host Input Pipeline</title><link href="https://kehang.github.io/blog/2024/battle-tested-llm-training-multihost-input-pipeline/" rel="alternate" type="text/html" title="Battle-Tested LLM Training: Multi-host Input Pipeline"/><published>2024-07-24T10:16:16+00:00</published><updated>2024-07-24T10:16:16+00:00</updated><id>https://kehang.github.io/blog/2024/battle-tested-llm-training-multihost-input-pipeline</id><content type="html" xml:base="https://kehang.github.io/blog/2024/battle-tested-llm-training-multihost-input-pipeline/"><![CDATA[<p>I recently discovered that the <a href="https://github.com/google/maxtext">MaxText</a> project serves as an excellent reference resource for learning about the latest developments in LLM training and inference. The README advertises it as a high-performance, highly scalable solution that achieves high Model FLOPs Utilization (MFUs) and is compatible with both TPUs and GPUs (for which we should likely thank the Jax team). If these claims are true, which I believe they are, this codebase should attract practitioners, researchers, and students to explore it and learn the best and latest practices.</p> <p>This brief post focuses on the input pipeline in a multi-host setting. You can roughly think of multi-host as running programs over multiple GPU hosts or TPU hosts (each host has one or more workers called devices). The input pipeline performs at two stages:</p> <ul> <li>stage 1: it distributes the dataset across all hosts, i.e., each host gets a shard of the dataset.</li> <li>stage 2: at the local dataset shard, during iteration, it distributes the batch across the accelerator devices under the host (each TPUv5e host has 4 accelerator devices, while each GPU host typically has 1 accelerator device).</li> </ul> <p>How do we achieve that in code? Since <a href="https://github.com/google/grain">grain</a> is becoming a popular choice (see <a href="https://github.com/google/maxtext/blob/main/getting_started/Data_Input_Pipeline.md">comparison to TFDS and HuggingFace</a>) in the Jax world, let’s dive into a grain implementation of such an input pipeline: <a href="https://github.com/google/maxtext/blob/ead18fbe6f2d8a6cbae6bbd38568146919e20e18/MaxText/input_pipeline/_grain_data_processing.py#L38">preprocessing_pipeline</a>. More concretely, let’s say we want to train a model with batch size 512 using 256 TPUv5e chips, i.e., 64 hosts.</p> <ul> <li>The first question is what batch size each shard should have, in our example, since global batch size is 512, each host is responsible for one 64th, so the local shard batch size is 8. (<a href="https://github.com/google/maxtext/blob/ead18fbe6f2d8a6cbae6bbd38568146919e20e18/MaxText/input_pipeline/_grain_data_processing.py#L78">shard batch size</a>):</li> </ul> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>batch_size=global_batch_size // jax.process_count()
</code></pre></div></div> <ul> <li>Then next question is which host gets which 64th shard of the dataset. This is specified by <code class="language-plaintext highlighter-rouge">grain.IndexSampler</code> and its <code class="language-plaintext highlighter-rouge">shard_options</code> argument (<a href="https://github.com/google/maxtext/blob/ead18fbe6f2d8a6cbae6bbd38568146919e20e18/MaxText/input_pipeline/_grain_data_processing.py#L84-L92">code-link</a>):</li> </ul> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>shard_options=grain.ShardOptions(
    shard_index=dataloading_host_index, shard_count=dataloading_host_count
)
</code></pre></div></div> <p>For the first host, <code class="language-plaintext highlighter-rouge">shard_index</code> is 0, and <code class="language-plaintext highlighter-rouge">shard_count</code> is 64.</p> <ul> <li> <p>Now for stage 2, each host will be distributing local batches of size 8 across 4 devices. MaxText has this iterator class <a href="https://github.com/google/maxtext/blob/ead18fbe6f2d8a6cbae6bbd38568146919e20e18/MaxText/multihost_dataloading.py#L93">MultiHostDataLoadIterator</a>, which takes in a dataloader and turns it into an iterator (<a href="https://github.com/google/maxtext/blob/ead18fbe6f2d8a6cbae6bbd38568146919e20e18/MaxText/multihost_dataloading.py#L102C29-L102C50">local_iterator</a>), and its <a href="https://github.com/google/maxtext/blob/ead18fbe6f2d8a6cbae6bbd38568146919e20e18/MaxText/multihost_dataloading.py#L119">__next__ method</a> will do the batch distribution.</p> </li> <li> <p>The actual heavy lifting (distributing) is done by <a href="https://github.com/google/maxtext/blob/ead18fbe6f2d8a6cbae6bbd38568146919e20e18/MaxText/multihost_dataloading.py#L50C5-L50C23">_form_global_array</a>. First, it <a href="https://github.com/google/maxtext/blob/ead18fbe6f2d8a6cbae6bbd38568146919e20e18/MaxText/multihost_dataloading.py#L55">splits</a> the batch array into N pieces (N is the number of local devices) and then <a href="https://github.com/google/maxtext/blob/ead18fbe6f2d8a6cbae6bbd38568146919e20e18/MaxText/multihost_dataloading.py#L63">put each piece to assigned device</a> in order. Finally, it informs jax that those local arrays form a global array (this becomes more relevant when we talk about <code class="language-plaintext highlighter-rouge">pjit</code> in future posts.)</p> </li> </ul> <p>Some final thought, it’s not always the case to distribute the host batch across all local devices; it depends on <a href="https://github.com/google/maxtext/blob/ead18fbe6f2d8a6cbae6bbd38568146919e20e18/MaxText/configs/base.yml#L203"><code class="language-plaintext highlighter-rouge">data_sharding</code> configuration</a>. We’ll probably dive deeper into this later, for now, distributing all the way to each local device is a good starting point.</p> <p><em>if you have comments or suggestions or spotted an error, please let me know via email: kehanghan at gmail dot com.</em></p>]]></content><author><name></name></author><category term="llm"/><summary type="html"><![CDATA[I recently discovered that the MaxText project serves as an excellent reference resource for learning about the latest developments in LLM training and inference. The README advertises it as a high-performance, highly scalable solution that achieves high Model FLOPs Utilization (MFUs) and is compatible with both TPUs and GPUs (for which we should likely thank the Jax team). If these claims are true, which I believe they are, this codebase should attract practitioners, researchers, and students to explore it and learn the best and latest practices.]]></summary></entry><entry><title type="html">A Desk That Listens</title><link href="https://kehang.github.io/blog/2022/a-desk-that-listens/" rel="alternate" type="text/html" title="A Desk That Listens"/><published>2022-12-26T10:16:16+00:00</published><updated>2022-12-26T10:16:16+00:00</updated><id>https://kehang.github.io/blog/2022/a-desk-that-listens</id><content type="html" xml:base="https://kehang.github.io/blog/2022/a-desk-that-listens/"><![CDATA[<p>As a continuation of my last post <a href="/blog/2022/a-desk-with-its-own-schedule/">A Desk with Its Own Schedule</a>, I’m building a new version of it; adding voice-control capability so that it listens to my commands.</p> <p>One might think it’s not really necessary to voice control a desk to go up and down since the buttons on the control panel already do these very easily. Two reasons for me to carry out this version:</p> <ul> <li> <p>introducing a new communication mechanism (i.e., natural language) to my desk is technically very interesting; as you will see, it involves designing a whole series of modules that work together such as wake word detection, speech recognition, intent classification and execution.</p> </li> <li> <p>adding voice-control actually makes it possible to have more sophisticated desk behaviors. For instance, asking the desk to rest for 10 minutes and then go back to work again, or start a working schedule at 10 am on Monday.</p> </li> </ul> <p>Here is how it looks: <a href="https://youtu.be/2v-3ZvoLdjo">video demo</a> (<a href="https://github.com/KEHANG/smart-desk">code</a>).</p> <iframe width="100%" height="360" src="https://www.youtube.com/embed/2v-3ZvoLdjo" frameborder="0" allowfullscreen=""></iframe> <h2 id="voice-control-workflow">Voice-control workflow</h2> <p>Just like Google Home Assistant, for voice control to work, we have to build a system of four components and coordinate them to work together.</p> <p class="srs_img"><img src="/assets/smart_desk_v2_post/img/workflow.png" alt="Alt text" width="100%"/></p> <ul> <li> <p>wake word detection module listens to the audio stream constantly and triggers speech recognition module when it picks up certain words (e.g., Hey Goolge).</p> </li> <li> <p>speech recognition module converts audio waveform into text representation.</p> </li> <li> <p>intent detection module takes in a text sentence and figures out the intent and associated parameters (e.g., <code class="language-plaintext highlighter-rouge">rest for 10 mins</code> can be classified as the intent of <code class="language-plaintext highlighter-rouge">going down</code> with argument <code class="language-plaintext highlighter-rouge">10 mins</code>).</p> </li> <li> <p>based on the intent, corresponding APIs can be used to acutally execute the actions.</p> </li> </ul> <p>I decided to use <code class="language-plaintext highlighter-rouge">Bob</code> as my wake word (so that becomes the name of my desk). Not to complicate things in the first try, I built the logic flow via the following code:</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Check if the wake word is present in the speech
</span><span class="k">if</span> <span class="nf">is_wake_word</span><span class="p">(</span><span class="n">text</span><span class="p">,</span> <span class="n">wake_word</span><span class="p">):</span>
	<span class="c1"># Wake word has been detected, do something...
</span>	<span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">Wake word detected:</span><span class="sh">"</span><span class="p">,</span> <span class="n">text</span><span class="p">)</span>
	<span class="n">intent</span><span class="p">,</span> <span class="n">kwargs</span> <span class="o">=</span> <span class="n">intent_detection</span><span class="p">.</span><span class="nf">detect_intent</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
	<span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">Intent detected:</span><span class="sh">"</span><span class="p">,</span> <span class="n">intent</span><span class="p">,</span> <span class="n">kwargs</span><span class="p">)</span>
	<span class="nf">execute</span><span class="p">(</span><span class="n">intent</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</code></pre></div></div> <h2 id="wake-word-detection">Wake word detection</h2> <p>In this prototype, my detector is made extremely simple using library <a href="https://github.com/Uberi/speech_recognition">Uberi/speech_recognition</a>: check if <code class="language-plaintext highlighter-rouge">Bob</code> appears in the text sentence recognized by <code class="language-plaintext highlighter-rouge">sr.Recognizer()</code>. For advanced use cases, one may find this library useful <a href="https://github.com/Picovoice/porcupine">Picovoice/porcupine</a>.</p> <h2 id="speech-recognition">Speech recognition</h2> <p><a href="https://github.com/Uberi/speech_recognition">Uberi/speech_recognition</a> provides multiple APIs including <code class="language-plaintext highlighter-rouge">CMU Sphinx</code>, <code class="language-plaintext highlighter-rouge">Google Speech Recognition</code>, <code class="language-plaintext highlighter-rouge">Microsoft Azure Speech</code> etc. I used <code class="language-plaintext highlighter-rouge">Google Speech Recognition</code> in this post and found that very easy to hook up with. The library even provides a generic API key so if you don’t use it too heavily you can call the API out of the box. If one feels like not to rely on those APIs from big tech, it’s not a bad idea to build a speech recognizer from scratch via machine learning. Here is an example <a href="https://www.youtube.com/watch?v=YereI6Gn3bM">speech recognition by Michael Phi</a>.</p> <h2 id="intent-detection">Intent detection</h2> <p>Intent detection is also a good place to utilize machine learning. Here again not to complicate things, I built a keyword-based solution to classify intents.</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Define the keywords that indicate each intent
</span><span class="n">stand_up_keywords</span> <span class="o">=</span> <span class="p">[</span><span class="sh">"</span><span class="s">stand</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">up</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">rest</span><span class="sh">"</span><span class="p">]</span>
<span class="n">sit_down_keywords</span> <span class="o">=</span> <span class="p">[</span><span class="sh">"</span><span class="s">sit</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">down</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">work</span><span class="sh">"</span><span class="p">]</span>
<span class="n">report_height_keywords</span> <span class="o">=</span> <span class="p">[</span><span class="sh">"</span><span class="s">height</span><span class="sh">"</span><span class="p">]</span>

<span class="c1"># Check if any of the keywords for each intent appear in the text
</span><span class="k">if</span> <span class="nf">any</span><span class="p">(</span><span class="n">word</span> <span class="ow">in</span> <span class="n">text</span> <span class="k">for</span> <span class="n">word</span> <span class="ow">in</span> <span class="n">stand_up_keywords</span><span class="p">):</span>
    <span class="n">intent</span> <span class="o">=</span> <span class="sh">"</span><span class="s">rest</span><span class="sh">"</span>
    <span class="c1"># Extract the time duration information from the command text
</span>    <span class="n">kwargs</span> <span class="o">=</span> <span class="p">{</span><span class="sh">"</span><span class="s">timeout</span><span class="sh">"</span><span class="p">:</span> <span class="nf">extract_duration</span><span class="p">(</span><span class="n">text</span><span class="p">)}</span>
<span class="bp">...</span>
<span class="bp">...</span>
</code></pre></div></div> <h2 id="acknowledgements">Acknowledgements</h2> <p>I’d like to thank a couple of projects here:</p> <ul> <li><a href="https://github.com/Uberi/speech_recognition">Uberi/speech_recognition</a> provides the backbone of my voice-control system.</li> <li>This is the first time I used a machine/AI for pair-programming, yes you guessed right <a href="https://chat.openai.com/chat">chatGPT</a>, which certainly made the whole development process delightful.</li> </ul>]]></content><author><name></name></author><category term="iot"/><summary type="html"><![CDATA[As a continuation of my last post A Desk with Its Own Schedule, I’m building a new version of it; adding voice-control capability so that it listens to my commands.]]></summary></entry><entry><title type="html">A Desk with Its Own Schedule</title><link href="https://kehang.github.io/blog/2022/a-desk-with-its-own-schedule/" rel="alternate" type="text/html" title="A Desk with Its Own Schedule"/><published>2022-11-25T10:16:16+00:00</published><updated>2022-11-25T10:16:16+00:00</updated><id>https://kehang.github.io/blog/2022/a-desk-with-its-own-schedule</id><content type="html" xml:base="https://kehang.github.io/blog/2022/a-desk-with-its-own-schedule/"><![CDATA[<p>Ever since I read Armin Ronarcher’s <a href="https://lucumr.pocoo.org/2020/5/24/my-standard-desktop/">post</a> on how he controlled his desk with shell commands, I’ve been wanting to connect to my standing desk and add some intelligence to it. The immediate challenge is how to connect. Armin uses desk’s bluetooth feature, but my <a href="https://amzn.to/3Ez3rH6">Flexispot desk</a> doesn’t have this, so I approached it with with physical connection: linking a Raspberry Pi to my desk, then programming the Pi to introduce customized behaviour.</p> <p>I tend to forget to stand up and rest during work, so the first version presented here is to give a schedule to my desk. Here is how it looks: <a href="https://youtu.be/lJAUM3Wqgdk">video demo</a> (<a href="https://github.com/KEHANG/smart-desk">code</a>).</p> <iframe width="100%" height="360" src="https://www.youtube.com/embed/lJAUM3Wqgdk" frameborder="0" allowfullscreen=""></iframe> <h2 id="high-level-connection">High level connection</h2> <p>A standing desk usually uses a control panel/pad to send commands (up, down, etc.) to the desk’s motor controller via a RJ45 cable (Ethernet cable).</p> <p class="srs_img"><img src="/assets/smart_desk_post/img/connection.png" alt="Alt text" width="100%"/></p> <p>In my case, I’ll replace the control panel with a Raspberry Pi (that way, we can implement some intelligence logic on the Pi side, which becomes the brain of our desk). How to connect? We can still use a RJ45 cable with an additional component: <a href="https://amzn.to/3OGtDEt">RJ45 breakout board</a>. The breakout board flattens the 8 pins of RJ45 in a way that’s super convenient to wire with Pi’s GPIO pins.</p> <h2 id="low-level-connection">Low level connection</h2> <p>Importantly we need to connect 4 pins of the RJ45 with 4 GPIO pins of the Pi. The exact mapping depends on the desk model. In my case, it’s a <a href="https://amzn.to/3Ez3rH6">Flexispot EC3</a> model which uses the HS11A-1 control panel. The information flows like this: command (in bytes format) gets emitted from the Pi’s pin 8 (i.e., TX), passes through RJ45 pin 6 and arrives in motor controller’s RX. data like desk height (in bytes format) get emitted from the motor controller’s TX, passes through RJ45 pin 5 and arrives in Pi’s pin 10 (i.e., RX).</p> <p class="srs_img"><img src="/assets/smart_desk_post/img/low-level-connection.png" alt="Alt text" width="100%"/></p> <p>Note other models using different control panels may have RJ45 pins arranged differently. (let me know if you’d like some details on how to debug pins for other models)</p> <h2 id="command-encoding">Command encoding</h2> <p>When commands are sent to controller, they have to be a stream of bytes. Thanks to <a href="https://github.com/nv1t/standing-desk-interceptor">nv1t/standing-desk-interceptor</a> and <a href="https://github.com/iMicknl/LoctekMotion_IoT">iMicknl/LoctekMotion_IoT</a>, below is a mapping between the major commands and their bytes format. For instance, the <code class="language-plaintext highlighter-rouge">up</code> command is encoded as <code class="language-plaintext highlighter-rouge">\x9b\x06\x02\x01\x00\xfc\xa0\x9d</code>.</p> <table> <thead> <tr> <th>Command name</th> <th>Start</th> <th>Length</th> <th>Type</th> <th>Payload</th> <th>Checksum</th> <th>End</th> </tr> </thead> <tbody> <tr> <td><code class="language-plaintext highlighter-rouge">up</code></td> <td><code class="language-plaintext highlighter-rouge">9b</code></td> <td><code class="language-plaintext highlighter-rouge">06</code></td> <td><code class="language-plaintext highlighter-rouge">02</code></td> <td><code class="language-plaintext highlighter-rouge">01</code> <code class="language-plaintext highlighter-rouge">00</code></td> <td><code class="language-plaintext highlighter-rouge">fc</code> <code class="language-plaintext highlighter-rouge">a0</code></td> <td><code class="language-plaintext highlighter-rouge">9d</code></td> </tr> <tr> <td><code class="language-plaintext highlighter-rouge">down</code></td> <td><code class="language-plaintext highlighter-rouge">9b</code></td> <td><code class="language-plaintext highlighter-rouge">06</code></td> <td><code class="language-plaintext highlighter-rouge">02</code></td> <td><code class="language-plaintext highlighter-rouge">02</code> <code class="language-plaintext highlighter-rouge">00</code></td> <td><code class="language-plaintext highlighter-rouge">0c</code> <code class="language-plaintext highlighter-rouge">a0</code></td> <td><code class="language-plaintext highlighter-rouge">9d</code></td> </tr> <tr> <td><code class="language-plaintext highlighter-rouge">preset 1</code></td> <td><code class="language-plaintext highlighter-rouge">9b</code></td> <td><code class="language-plaintext highlighter-rouge">06</code></td> <td><code class="language-plaintext highlighter-rouge">02</code></td> <td><code class="language-plaintext highlighter-rouge">04</code> <code class="language-plaintext highlighter-rouge">00</code></td> <td><code class="language-plaintext highlighter-rouge">ac</code> <code class="language-plaintext highlighter-rouge">a3</code></td> <td><code class="language-plaintext highlighter-rouge">9d</code></td> </tr> <tr> <td><code class="language-plaintext highlighter-rouge">preset 2</code></td> <td><code class="language-plaintext highlighter-rouge">9b</code></td> <td><code class="language-plaintext highlighter-rouge">06</code></td> <td><code class="language-plaintext highlighter-rouge">02</code></td> <td><code class="language-plaintext highlighter-rouge">08</code> <code class="language-plaintext highlighter-rouge">00</code></td> <td><code class="language-plaintext highlighter-rouge">ac</code> <code class="language-plaintext highlighter-rouge">a6</code></td> <td><code class="language-plaintext highlighter-rouge">9d</code></td> </tr> <tr> <td><code class="language-plaintext highlighter-rouge">preset 3</code></td> <td><code class="language-plaintext highlighter-rouge">9b</code></td> <td><code class="language-plaintext highlighter-rouge">06</code></td> <td><code class="language-plaintext highlighter-rouge">02</code></td> <td><code class="language-plaintext highlighter-rouge">10</code> <code class="language-plaintext highlighter-rouge">00</code></td> <td><code class="language-plaintext highlighter-rouge">ac</code> <code class="language-plaintext highlighter-rouge">ac</code></td> <td><code class="language-plaintext highlighter-rouge">9d</code></td> </tr> </tbody> </table> <p>Note the <code class="language-plaintext highlighter-rouge">preset</code> commands make the desk go to the preset heights.</p> <h2 id="height-data-decoding">Height data decoding</h2> <p>In reverse direction, height data gets sent from controller to the Pi’s RX pin. The data is also encoded in bytes. Below is an example packet.</p> <table> <thead> <tr> <th>Prefix</th> <th> </th> <th>Encoded height</th> <th> </th> <th>Suffix</th> </tr> </thead> <tbody> <tr> <td><code class="language-plaintext highlighter-rouge">\x9b\x07\x12</code></td> <td> </td> <td><code class="language-plaintext highlighter-rouge">\x5b\xff\x06</code></td> <td> </td> <td><code class="language-plaintext highlighter-rouge">\x99\x24\x9d</code></td> </tr> </tbody> </table> <p>The encoded height has three byte characters, each representing a digit in the height. For instance, <code class="language-plaintext highlighter-rouge">\x06</code> → <code class="language-plaintext highlighter-rouge">1</code> (read futher on <a href="https://alselectro.wordpress.com/2015/03/03/8051-tutorials-3-interfacing-7-segment-display/">why such mapping is made</a>). I’m listing the mapping of 10 digits to the bytes down below.</p> <table> <thead> <tr> <th>Byte</th> <th><code class="language-plaintext highlighter-rouge">\x3f</code></th> <th><code class="language-plaintext highlighter-rouge">\x06</code></th> <th><code class="language-plaintext highlighter-rouge">\x5b</code></th> <th><code class="language-plaintext highlighter-rouge">\x4f</code></th> <th><code class="language-plaintext highlighter-rouge">\x66</code></th> <th><code class="language-plaintext highlighter-rouge">\x6d</code></th> <th><code class="language-plaintext highlighter-rouge">\x7c</code></th> <th><code class="language-plaintext highlighter-rouge">\x07</code></th> <th><code class="language-plaintext highlighter-rouge">\x7f</code></th> <th><code class="language-plaintext highlighter-rouge">\x6f</code></th> </tr> </thead> <tbody> <tr> <td>Digit</td> <td><code class="language-plaintext highlighter-rouge">0</code></td> <td><code class="language-plaintext highlighter-rouge">1</code></td> <td><code class="language-plaintext highlighter-rouge">2</code></td> <td><code class="language-plaintext highlighter-rouge">3</code></td> <td><code class="language-plaintext highlighter-rouge">4</code></td> <td><code class="language-plaintext highlighter-rouge">5</code></td> <td><code class="language-plaintext highlighter-rouge">6</code></td> <td><code class="language-plaintext highlighter-rouge">7</code></td> <td><code class="language-plaintext highlighter-rouge">8</code></td> <td><code class="language-plaintext highlighter-rouge">9</code></td> </tr> </tbody> </table> <p>It’s worth noting that a digit with a decimal point is encoded differently than a pure digit. So here’s another mapping just for that.</p> <table> <thead> <tr> <th>Byte</th> <th><code class="language-plaintext highlighter-rouge">\xbf</code></th> <th><code class="language-plaintext highlighter-rouge">\x86</code></th> <th><code class="language-plaintext highlighter-rouge">\xdb</code></th> <th><code class="language-plaintext highlighter-rouge">\xcf</code></th> <th><code class="language-plaintext highlighter-rouge">\xe6</code></th> <th><code class="language-plaintext highlighter-rouge">\xed</code></th> <th><code class="language-plaintext highlighter-rouge">\xfc</code></th> <th><code class="language-plaintext highlighter-rouge">\x87</code></th> <th><code class="language-plaintext highlighter-rouge">\xff</code></th> <th><code class="language-plaintext highlighter-rouge">\xef</code></th> </tr> </thead> <tbody> <tr> <td>Digit</td> <td><code class="language-plaintext highlighter-rouge">0.</code></td> <td><code class="language-plaintext highlighter-rouge">1.</code></td> <td><code class="language-plaintext highlighter-rouge">2.</code></td> <td><code class="language-plaintext highlighter-rouge">3.</code></td> <td><code class="language-plaintext highlighter-rouge">4.</code></td> <td><code class="language-plaintext highlighter-rouge">5.</code></td> <td><code class="language-plaintext highlighter-rouge">6.</code></td> <td><code class="language-plaintext highlighter-rouge">7.</code></td> <td><code class="language-plaintext highlighter-rouge">8.</code></td> <td><code class="language-plaintext highlighter-rouge">9.</code></td> </tr> </tbody> </table> <p>Thus the above example of encoded height <code class="language-plaintext highlighter-rouge">\x5b\xff\x06</code> gets decoded to <code class="language-plaintext highlighter-rouge">28.1</code> inch.</p> <h2 id="scheduling">Scheduling</h2> <p>Based on my personal habit, I created <a href="https://github.com/KEHANG/smart-desk/blob/main/schedule.py">this scheduling script</a> to split each hour for my standing desk: 50 min work + 10 min rest. Each day the desk runs 8 rounds of work and rest. A crontab job is scheduled to run this script on desired days.</p> <h2 id="acknowledgements">Acknowledgements</h2> <p>I’d like to thank several projects here:</p> <ul> <li><a href="https://github.com/iMicknl/LoctekMotion_IoT">LoctekMotion_IoT</a> has a very good README, summarizes findings from other projects and covers a lot of basics.</li> <li>I learned how to decode the height data from <a href="https://github.com/VinzSpring/LoctekReverseengineering">LoctekReverseengineering</a>.</li> <li><a href="https://alselectro.wordpress.com/2015/03/03/8051-tutorials-3-interfacing-7-segment-display/">alselectro</a> has a detailed and educational post on how a <code class="language-plaintext highlighter-rouge">digit</code> gets rendered via 7-segment display, basically why <code class="language-plaintext highlighter-rouge">1</code> is represented by <code class="language-plaintext highlighter-rouge">\x06</code>.</li> </ul>]]></content><author><name></name></author><category term="iot"/><summary type="html"><![CDATA[Ever since I read Armin Ronarcher’s post on how he controlled his desk with shell commands, I’ve been wanting to connect to my standing desk and add some intelligence to it. The immediate challenge is how to connect. Armin uses desk’s bluetooth feature, but my Flexispot desk doesn’t have this, so I approached it with with physical connection: linking a Raspberry Pi to my desk, then programming the Pi to introduce customized behaviour.]]></summary></entry><entry><title type="html">Demystifying Named Entity Recognition - Part II</title><link href="https://kehang.github.io/blog/2019/named-entity-recognition-part2/" rel="alternate" type="text/html" title="Demystifying Named Entity Recognition - Part II"/><published>2019-06-15T10:16:16+00:00</published><updated>2019-06-15T10:16:16+00:00</updated><id>https://kehang.github.io/blog/2019/named-entity-recognition-part2</id><content type="html" xml:base="https://kehang.github.io/blog/2019/named-entity-recognition-part2/"><![CDATA[<p>As a continuation for <a href="/blog/2019/named-entity-recognition/">Demystifying Named Entity Recognition - Part I</a>, in this post I’ll discuss popular models available in the field and try to cover:</p> <ul> <li> <p>popular <strong>traditional</strong> models</p> </li> <li> <p><strong>deep learning</strong> models</p> </li> <li> <p>python libraries</p> </li> </ul> <p>Over the history of <a href="https://en.wikipedia.org/wiki/Named-entity_recognition">NER</a>, there’s been three major approaches: grammar-based, dictionary-based and machine-learning-based. Grammar-based approach produces a set of empirical rules hand-crafted by experienced computational linguists, usually takes months of work. Dictionary-based approach basically organizes all the known entities into a lookup table, which can be used to detect whether a candidate belongs to a defined category or not. By design it doesn’t work well with newly invented entities. Machine-learning-based approach typically needs annotated data, but doesn’t necessarily rely on domain experts to come up with rules or fail on unseen entities.</p> <p>This post focuses only on machine-learning based models.</p> <h2 id="1-popular-traditional-models">1. Popular traditional models</h2> <p>The traditional models we’ll discuss here are <a href="https://en.wikipedia.org/wiki/Maximum-entropy_Markov_model">MEMM</a>, <a href="https://en.wikipedia.org/wiki/Conditional_random_field">CRF</a>. They are very popularly used before deep learning models entered the scene.</p> <h3 id="11-memm">1.1 MEMM</h3> <p>We’ve covered the details of MEMM in the <a href="/blog/2019/named-entity-recognition/">previous post</a>. The key idea of the MEME approach is to model the <strong>conditional probability</strong> of tag sequenece for a given sentence with Markov assumption:</p> \[p(y_1...y_n | x_1...x_n) = \prod_{i=1}^{n} p(y_i |y_{i-1}, x_1...x_n)\] <p>We then model \(p(y_i | y_{i-1}, x_1...x_n)\) using local environment:</p> \[p(y_i | y_{i-1}, x_1...x_n) = \frac{\exp({\underline{\theta} \cdot \underline{f}(y_{i-1}, y_i, x_1...x_n)})}{\sum_{y'}{\exp({\underline{\theta} \cdot \underline{f}(y_{i-1}, y', x_1...x_n)})}}\] <p><strong>In inference</strong>, we use <em>Viterbi</em> algorithm to get best-fitting tag sequence for a given sentence. Details can be found in 2.2.1 section of the <a href="/blog/2019/named-entity-recognition/">previous post</a>.</p> <p><strong>In training</strong>, we use <em>maximum likelihood estimation</em> to get optimal \(\underline{\theta}\) that</p> \[max_{\underline{\theta}} \prod_{j=1}^{N} p(\underline{x}^j | \underline{y}^j)\] <p>where \(\underline{x}^j, \underline{y}^j\) are the \(j^{th}\) sentence and corresponding tag sequence (the whole training dataset has \(N\) examples).</p> <h3 id="12-crf">1.2 CRF</h3> <p>Instead of \(p(y_i | y_{i-1}, \underline{x})\) , Conditional Random Field (CRF) approach chooses to directly model \(p(\underline{y} | \underline{x})\):</p> \[p(\underline{y} | \underline{x}) = \frac{\exp({\underline{\Theta} \cdot \underline{F}(\underline{x}, \underline{y})})}{\sum_{\underline{y}'}{\exp({\underline{\Theta} \cdot \underline{F}(\underline{x}, \underline{y}')})}}\] <p>The main challenge in direct modeling is that the denominator is sum of \(K^n\) terms where \(K\) is the number of tag label types and \(n\) is the length of sentence to tag. This is a much larger number than that in MEMM - \(p(y_i | y_{i-1}, x_1...x_n)\) has just \(K\) terms in the denominator.</p> <h4 id="121-inference">1.2.1 Inference</h4> <p>During inference, we are only interested in the \(\underline{y}^{*}\) that gives the highest probability rather than the highest probability itself:</p> \[\underline{y}^{*} = \text{arg} \max_{\underline{y}} p(\underline{y} | \underline{x}) = \text{arg} \max_{\underline{y}}\exp({\underline{\Theta} \cdot \underline{F}(\underline{x}, \underline{y})}) = \text{arg} \max_{\underline{y}}{\underline{\Theta} \cdot \underline{F}(\underline{x}, \underline{y})}\] <p>If using brutal force, we have to evaluate \(\exp({\underline{\Theta} \cdot \underline{F}(\underline{x}, \underline{y})})\) for \(K^n\) times.</p> <p>Fortunately, if we add a little structure into \(\underline{F}(\underline{x}, \underline{y})\), which I’m going to talk about next, we can bring the exponential complexity - \(O(K^n)\) down to linear complexity - \(O(K^2n)\).</p> <p>The structure added in CRF is:</p> \[\underline{F}(\underline{x}, \underline{y}) = \sum_{i=1}^n \underline{f}(\underline{x}, y_{i-1}, y_i)\] <p>To maximize \(\underline{\Theta} \cdot \underline{F}(\underline{x}, \underline{y})\), we define a partial score as we did in 2.2.1 section of the <a href="/blog/2019/named-entity-recognition/">previous post</a>:</p> \[s_{partial, k}(y_{1...k}) = \underline{\Theta} \cdot \sum_{i=1}^k \underline{f}(\underline{x}, y_{i-1}, y_i)\] <p>If we can maximize any partial score (which turns out not that difficult), then the score we want to acutally maximize, \(\underline{\Theta} \cdot \underline{F}(\underline{x}, \underline{y})\), is just a special case of \(s_{partial, k}\) when \(k=n\).</p> <p>So how to maximize any partial score? Let’s start with \(k=1\), namely \(s_{partial, 1} (y_1)= \underline{\Theta} \cdot \underline{f}(\underline{x}, y_1)\).</p> <blockquote> <p>This is easy because it’s just a single-variable optimization and \(y_1\) can only have K choices. We also store all the evaluated \(s_{partial, 1} (y_1)\).</p> </blockquote> <p>How about \(k=2\), namely maximize \(s_{partial, 2}(y_1, y_2) = s_{partial, 1}(y_1) + \underline{\Theta} \cdot \underline{f}(\underline{x}, y_1, y_2)\)?</p> <blockquote> <p>We can first fix \(y_2\) and optimize over \(y_1\) dimension. Remember we’ve known \(s_{partial, 1}(y_1)\) evaluated from the previous question. So it takes \(K\) computations to find the optimal \(y_1\) for each \(y_2\) - \(s_{partial, 2}(y_1^*, y_2)\). Then pick the \(y_2^*\) which has maximum \(s_{partial, 2}(y_1^*, y_2)\). In total, we need perform \(K^2\) evaluations. We also store all the \(s_{partial, 2}(y_1^*, y_2)\) for future use.</p> </blockquote> <p>How about \(k=3\), namely maximize \(s_{partial, 3}(y_1, y_2, y_3) = s_{partial, 2}(y_1, y_2) + \underline{\Theta} \cdot \underline{f}(\underline{x}, y_2, y_3)\)?</p> <blockquote> <p>Similar to the previous question, we try to estimate \(s_{partial, 3}(y_1^*, y_2^*, y_3)\) for each \(y_3\) using \(s_{partial, 3}(y_1^*, y_2^*, y_3) = \max_{y_2}(s_{partial, 2}(y_1^*, y_2) + \underline{\Theta} \cdot \underline{f}(\underline{x}, y_2, y_3))\). We also carry out \(K\) evaluation per \(y_3\), thus totally \(K^2\) evaluations for all possible \(y_3\). We store \(s_{partial, 3}(y_1^*, y_2^*, y_3)\) for future use (e.g., when \(k=4\)).</p> </blockquote> <p>By doing this all the way to \(k=n\), we can get \(\max_{\underline{y}}{\underline{\Theta} \cdot \underline{F}(\underline{x}, \underline{y})}\) with roughly \(K^2n\) evaluations.</p> <h4 id="122-training">1.2.2 Training</h4> <p>Similar to MEMM, we can also use <em>maximum likelihood estimation</em> to get optimal \(\underline{\Theta}\) that</p> \[max_{\underline{\Theta}} \prod_{j=1}^{N} p(\underline{x}^j | \underline{y}^j)\] <p>where \(\underline{x}^j, \underline{y}^j\) are the \(j^{th}\) sentence and corresponding tag sequence (the whole training dataset has \(N\) examples). More details on training algorithm can be found in Page 10 of <a href="http://www.cs.columbia.edu/~mcollins/crf.pdf">Michael Collins’s CRF note</a>.</p> <h2 id="2-deep-learning-models">2. Deep learning models</h2> <p>The deep learning models we’ll discuss here are <a href="https://en.wikipedia.org/wiki/Long_short-term_memory">LSTM</a>, <a href="https://arxiv.org/abs/1508.01991">BiLSTM-CRF</a>, <a href="https://arxiv.org/pdf/1810.04805.pdf">Bert</a>.</p> <h3 id="21-lstm">2.1 LSTM</h3> <h4 id="211-architecture">2.1.1 Architecture</h4> <p class="srs_img"><img src="/assets/ner_post/img/lstm.png" alt="Alt text" width="100%"/></p> <p>In the setting of LSTM, each token \(x_i\) is fed to a LSTM unit, which outputs a \(o_i\). \(o_i\) models log probabilities of all possible tags at i-th position, so it has dimension of \(K\).</p> \[o_i = \begin{bmatrix}log P(y_i = PER | \underline{x})\\log P(y_i = ORG | \underline{x})\\...\\log P(y_i = MISC | \underline{x})\end{bmatrix}\] <h4 id="212-inference">2.1.2 Inference</h4> <p>The inference in LSTM is very simple: \(y_i\) = the tag with highest log probability at i-th position.</p> \[y_i^* = argmax_k o_{i,k}\] <p>which indicates the prediction of i-th position only utilizes the sentence information up to i-th token - only the left side of the sentence is used for tag prediction at i-th position. BiLSTM is designed to provide context information from both sides, which will be seen in next section.</p> <h4 id="213-training">2.1.3 Training</h4> <p>Like all the other neural network training, LSTM training uses <strong>Stochastic Gradient Descent</strong> algorithm. Loss function adopts <strong>negative log likelihood</strong>. For a data point \((\underline{x^j}, \underline{y^j})\), we have its loss calculated as:</p> \[L_j = -\sum_{i=1}^{n_j} o_i^j[y_{i}^j]\] <p>where \(n_j\) is the length of the sentence \(x^j\), \(o_i^j\) is the LSTM output at i-th position and \(y_i^j\) is the ground truth tag at i-th position.</p> <p><strong>Total loss</strong> is the mean of all the individual losses.</p> \[L = \frac{1}{N}\sum_{j=1}^N L_j\] <p>where \(N\) is the total number of training examples.</p> <h3 id="22-bilstm">2.2 BiLSTM</h3> <p class="srs_img"><img src="/assets/ner_post/img/bilstm.png" alt="Alt text" width="120%"/></p> <p>BiLSTM stands for bi-directional LSTM, which provides sequence information from both directions. Because of that, BiLSTM is more powerful than LSTM. Except the bi-directional component, the meaning of network output, inference, and training loss are same as LSTM.</p> <h3 id="23-bilstm-crf">2.3 BiLSTM-CRF</h3> <p>BiLSTM captures contextual information around i-th position. But at each position, BiLSTM predicts tags basically in an independent fashion. There’s cases where some adjacent positions are predicted with tags which do not usually appear together in reality. For example, I-PER tag should not follow B-ORG. To account for this kind of interactions between adjacent tags, Conditional Random Field (CRF) is introduced to BiLSTM.</p> <h4 id="231-architecture">2.3.1 Architecture</h4> <p class="srs_img"><img src="/assets/ner_post/img/bilstm-crf.png" alt="Alt text" width="120%"/></p> <p>where \(o_i\) models <strong>emission scores</strong> of all possible tags at i-th position and \(y_i^*\) is the best tag for i-th position which collectively achieves highest sequence score.</p> \[o_i = \begin{bmatrix} score_{emission}(y_i = PER | \underline{x})\\score_{emission}(y_i = ORG | \underline{x})\\...\\score_{emission}(y_i = MISC | \underline{x})\end{bmatrix}\] <p>CRF layer also learns a transition matrix \(A\) which stores transition scores between any possible pair of tag types.</p> <h4 id="231-inference">2.3.1 Inference</h4> <p>Same as the inference in CRF section, given a trained network and sentence \(\underline{x}\), any sequence \(\underline{s}\) will have a score.</p> \[score(\underline{x}, \underline{s}) = \sum_{i=1}^n o_i[s_i] + A[s_{i-1}][s_i]= \sum_{i=1}^n \phi(\underline{x}, s_{i-1}, s_i)\] <p>The score is a sum of contributions from token level. i-th position has contribution of \(\phi(\underline{x}, s_{i-1}, s_i) = o_i[s_i] + A[s_{i-1}][s_i]\), where the first term is emission score and second term is transition score.</p> <p>To find the tag sequence \(\underline{y}^*\) achieving highest score, we need to use dynamic programming.</p> <p>Define sub problem \(DP(k,t)\) to be the max score accumulated from 1st position to \(k\)-th position with the \(k\)-th position tag being \(t\), detailed as follows:</p> \[DP(k, t) = \max \limits_{\underline{s}\in S^k:s_k=t} \sum_{i=1}^k \phi(\underline{x}, s_{i-1}, s_i)\] <p>The recursion would be:</p> \[DP(k+1, t) = \max \limits_{t'} [DP(k, t') + \phi(\underline{x}, t', t)]\] <p>The original problem is then</p> \[score(\underline{x}, \underline{y}^*) = \max \limits_{t} DP(n, t)\] <p>We can always use <a href="https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-006-introduction-to-algorithms-fall-2011/lecture-videos/MIT6_006F11_lec20.pdf">parent pointers</a> to retrieve the corresponding best sequence \(\underline{y}^*\).</p> <h4 id="232-training">2.3.2 Training</h4> <p>Loss function for BiLSTM-CRF also adopts <strong>negative log likelihood</strong>. For a data point \((\underline{x^j}, \underline{y^j})\), we have its loss calculated as:</p> \[L_j = -log P(\underline{y}^j | \underline{x}^j) = - log \frac{exp(score(\underline{x}^j, \underline{y}^j))}{\sum \limits_{\underline{y'}^j} exp(score(\underline{x}^j, \underline{y'}^j))}\] \[= - score(\underline{x}^j, \underline{y}^j) + log \sum \limits\_{\underline{y'}^j} exp(score(\underline{x}^j, \underline{y'}^j))\] <p>where the first term is easy to calculate via a forward pass of the network and the second term needs more care. Let’s define that term (without log) as \(Z\), which is exponential sum of scores of all the possible sequences \(\underline{s}\) of length \(n\).</p> \[Z = \sum \limits_{\underline{s} \in S^n} exp(score(\underline{x}, \underline{s})) = \sum \limits_{\underline{s} \in S^n} exp(\sum_{i=1}^{n} \phi(\underline{x}, s_{i-1}, s_i))\] \[= \sum \limits_{\underline{s} \in S^n} \prod_{i=1}^{n} exp(\phi(\underline{x}, s_{i-1}, s_i)) = \sum \limits_{\underline{s} \in S^n} \prod_{i=1}^{n} \psi(\underline{x}, s_{i-1}, s_i)\] <p>To calculate \(Z\), we need to use dynamic programming again. This time the sub-problem \(DP(k,t)\) is the exponential sum of scores of all possible sequences of length \(k\) with last tag \(s_k = t\):</p> \[DP(k,t)= \sum \limits*{\underline{s} \in S^k: s_k=t} \prod*{i=1}^{k} \psi(\underline{x}, s\_{i-1}, s_i)\] <p>The recursion would be:</p> \[DP(k+1,t) = \sum \limits\_{t'} DP(k,t')\cdot \psi(\underline{x}, t', t)\] <p>The original problem is then</p> \[Z = \sum \limits_{t} DP(n,t)\] <p>Via this way, individual loss \(L_j\) is calculated and then batch loss by averaging the individual losses in the batch.</p> <h3 id="24-bert">2.4 Bert</h3> <p>Recent research on BERT provides an option for NER modeling. Despite of the complexity of the BERT model architecture, in the context of NER it can be regarded as an advanced version of our BiLSTM model - replacing the LSTM with multiple <a href="http://jalammar.github.io/illustrated-transformer/">Transformer Encoder</a> layers.</p> <p class="srs_img"><img src="/assets/ner_post/img/bert.png" alt="Alt text" width="120%"/></p> <p>Thus, \(o_i\) still models log probabilities of all possible tags at i-th position.</p> \[o_i = \begin{bmatrix}log P(y_i = PER | \underline{x})\\log P(y_i = ORG | \underline{x})\\...\\log P(y_i = MISC | \underline{x})\end{bmatrix}\] <p>Inference, and training loss are same as LSTM section.</p> <h2 id="3-python-libraries">3. Python libraries</h2> <p>There’s several machine learning based NER repositories in GitHub. I picked some of them here with some comments.</p> <ul> <li> <p><a href="https://github.com/KEHANG/ner/">KEHANG/ner</a>: for English texts, based on PyTorch, has LSTM, BiLSTM, BiLSTM+CRF and Bert models, has released conda package</p> </li> <li> <p><a href="https://github.com/shiyybua/NER">shiyybua/NER</a>: for Chinese texts, based on Tensorflow, only BiLSTM+CRF model, no packages released</p> </li> <li> <p><a href="https://github.com/Franck-Dernoncourt/NeuroNER">Franck-Dernoncourt/NeuroNER</a>: for English texts, based on Tensorflow, has LSTM model, no package released</p> </li> </ul>]]></content><author><name></name></author><category term="nlp,"/><category term="explained"/><summary type="html"><![CDATA[As a continuation for Demystifying Named Entity Recognition - Part I, in this post I’ll discuss popular models available in the field and try to cover:]]></summary></entry><entry><title type="html">Demystifying Named Entity Recognition - Part I</title><link href="https://kehang.github.io/blog/2019/named-entity-recognition/" rel="alternate" type="text/html" title="Demystifying Named Entity Recognition - Part I"/><published>2019-05-14T17:16:16+00:00</published><updated>2019-05-14T17:16:16+00:00</updated><id>https://kehang.github.io/blog/2019/named-entity-recognition</id><content type="html" xml:base="https://kehang.github.io/blog/2019/named-entity-recognition/"><![CDATA[<p>Recently I’ve been working on a project related to <strong>Named Entity Recognition</strong> (<strong>NER</strong>). At the very beginning, I was trying to find a well-explained document to get myself started, but couldn’t do so (instead I found redundant pieces here and there on the Internet). My requirement is simple. It should include</p> <ul> <li> <p><em>what</em> is <strong>NER</strong></p> </li> <li> <p><em>how</em> to <strong>formulate</strong> it</p> </li> <li> <p><em>what</em> are the <strong>traditional</strong> and <strong>start-of-the-art</strong> models</p> </li> <li> <p><em>what</em> are the <strong>off-the-shelf</strong> options</p> </li> <li> <p><em>how</em> to build a <strong>customized</strong> NER model</p> </li> </ul> <p>So this post will try to provide a complete set of explanation on these questions.</p> <h2 id="1-what-is-ner">1. What is NER</h2> <p>Simply put, <strong>Named Entity Recognition</strong> is a technology that identifies <strong>certain entities</strong> from a sentence/paragraph/document. Like the sentence below, <strong>NER</strong> tries to tag each word with a label; <code class="language-plaintext highlighter-rouge">Steve Jobs</code> and <code class="language-plaintext highlighter-rouge">Steve Wozniak</code> to be <code class="language-plaintext highlighter-rouge">PER</code> (persion entity), <code class="language-plaintext highlighter-rouge">Apple</code> to be <code class="language-plaintext highlighter-rouge">ORG</code> (organization entity) and the rest <code class="language-plaintext highlighter-rouge">O</code> (not an entity).</p> <p class="srs_img"><img src="/assets/ner_post/img/what-is-ner.png" alt="Alt text" width="80%"/></p> <p>This is useful in lots of cases. For instance, <em>Apple Mail</em> identifies <code class="language-plaintext highlighter-rouge">TIME</code> entity in emails and makes pulling events from email to calendar much easier than before; <em>Google Search</em> is able to find <code class="language-plaintext highlighter-rouge">Headquarters</code> entity in a relevent document to answer query questions.</p> <p class="srs_img"><img src="/assets/ner_post/img/what-is-ner-2.png" alt="Alt text" width="80%"/></p> <p>So how does it actually work?</p> <h2 id="2-mathematical-formulation">2. Mathematical Formulation</h2> <p>Let’s step back and see what <strong>NER</strong> is doing essentially. For a given sentence \(x_1 ... x_n\), <strong>NER</strong> decides to tag each word \(x_i\) with an entity label \(y_i\).</p> <p class="srs_img"><img src="/assets/ner_post/img/math-formulation-1.png" alt="Alt text" width="80%"/></p> <p>One obvious challenge here is one word should be tagged differently depending on its <strong>context</strong>. E.g., Apple in the example above is an organization entity, but in other contexts might be a fruit entity.</p> <p>So how can we tag wisely? Basically we need two things.</p> <ul> <li> <p>a <strong>score function</strong> \(s(y_1...y_n, x_1...x_n)\), which splits out a score measuring how fit a tagging sequence \(y_1 ... y_n\) is to a given sentence \(x_1 ... x_n\). A well-designed score function should assign a higher score to a tagging sequence that fits better.</p> </li> <li> <p>a <strong>solver</strong>, which is able to pick the highest-scoring tagging sequence among overwhelmingly large number of possible candidates. Just take a sentence with \(7\) words as an example, we are talking about \(10^7\) tagging candidates if there’s 10 unique entities to choose from. A good solver should be able to efficiently get to the best tagging sequence.</p> </li> </ul> <h3 id="21-score-function">2.1 Score Function</h3> <p>Researchers in the field like to use probability model to build score function: a better-fitting sequence can be given a higher probability. Often times we choose <strong>conditional probability</strong> (one can use joint probablity as well), like below</p> \[s(y_1...y_n, x_1...x_n) = p(y_1...y_n | x_1...x_n) = \prod_{i=1}^{n} p(y_i |y_1...y_{i-1}, x_1...x_n)\] <p>If we make a simplication \(p(y_i |y_1...y_{i-1}, x_1...x_n) \approx p(y_i | y_{i-1}, x_1...x_n)\), then</p> \[s(y_1...y_n, x_1...x_n) = \prod_{i=1}^{n} p(y_i |y_{i-1}, x_1...x_n)\] <p>Now the question becomes how do we model</p> \[p(y_i | y_{i-1}, x_1...x_n)\] <h4 id="211-model-py_i--y_i-1-x_1x_n">2.1.1 Model \(p(y_i | y_{i-1}, x_1...x_n)\)</h4> <p>Following the above example, we are basically asking <em>how likely Jobs is PER / ORG / MISC / O, if Steve is PER?</em></p> <p class="srs_img"><img src="/assets/ner_post/img/math-formulation-2.png" alt="Alt text" width="80%"/></p> <p>A natural thought would be to create a <strong>second</strong> score function (let’s call it local score function since it looks only at \(y_i, y_{i-1}\)) with well-defined local features \(\vec{f}(y_{i-1}, y_i, x_1...x_n)\):</p> \[s_{local}(y_{i-1}, y_i, x_1...x_n) = \vec{\theta} \cdot \vec{f}(y_{i-1}, y_i, x_1...x_n)\] <p>and define probability based on the local score function:</p> \[p(y_i | y_{i-1}, x_1...x_n) = \frac{\exp{s_{local}(y_{i-1}, y_i, x_1...x_n)}}{\sum_{y'}{\exp{s_{local}(y_{i-1}, y', x_1...x_n)}}} = \frac{\exp({\vec{\theta} \cdot \vec{f}(y_{i-1}, y_i, x_1...x_n)})}{\sum_{y'}{\exp({\vec{\theta} \cdot \vec{f}(y_{i-1}, y', x_1...x_n)})}}\] <p>\(\vec{f} \in R^m\), is an m-dimension vector, meaning there’s <strong>m</strong> predefined feature functions \(f_1...f_m\). Some feature examples could be:</p> \[\begin{align*} f_1(y_{i-1}, y_i, x_1...x_n) = \left\{ \begin{array}{ccc} 1 &amp; \text{if $x_{i-1}, x_i$ are capitalized, $y_{i-1}, y_i=\text{PER}$}\\ 0 &amp; \text{otherwise} \end{array} \right. \end{align*}\] \[\begin{align*} f_2(y_{i-1}, y_i, x_1...x_n) = \left\{ \begin{array}{ccc} 1 &amp; \text{if $x_i$ ends in ing, $y_i=\text{O}$}\\ 0 &amp; \text{otherwise} \end{array} \right. \end{align*}\] \[...\] <p>At this point, once we have defined \(\vec{f}\) and picked the weights \(\theta\) for the features, we can readily calculate \(p(y_i | y_{i-1}, x_1...x_n)\) and tagging score from</p> \[p(y_i | y_{i-1}, x_1...x_n) = \frac{\exp({\vec{\theta} \cdot \vec{f}(y_{i-1}, y_i, x_1...x_n)})}{\sum_{y'}{\exp({\vec{\theta} \cdot \vec{f}(y_{i-1}, y', x_1...x_n)})}}\] \[s(y_1...y_n, x_1...x_n) = \prod_{i=1}^{n} \frac{\exp({\vec{\theta} \cdot \vec{f}(y_{i-1}, y_i, x_1...x_n)})}{\sum_{y'}{\exp({\vec{\theta} \cdot \vec{f}(y_{i-1}, y', x_1...x_n)})}}\] <h4 id="212-three-remaining-questions">2.1.2 Three remaining questions</h4> <p>Let’s summarize here. With this framework set up, there’s only <strong>3</strong> questions remaining:</p> <ul> <li>how to design feature functions \(\vec{f}(y_{i-1}, y_i, x_1...x_n)\)?</li> </ul> <blockquote> <p>Traditionally it’s kinda art and requires expert knowledge in the problem domain. But recent deep learning approaches such as LSTM, BiLSTM aim to utilize large neural networks and automatically figure out suitable featurization from data.</p> </blockquote> <ul> <li>how to estimate weights \(\vec{\theta}\)?</li> </ul> <blockquote> <p>\(\vec{\theta}\) can be obtained by training on data. One intuitive and popularly used method is <em>Maximum Likelihood Estimation</em>.</p> </blockquote> <ul> <li>assuming we already know \(\vec{f}, \vec{\theta}\), how to get the best fitting tag sequence \(y_1^{*}...y_n^{*}\) for a sentence \(x_1...x_n\)?</li> </ul> <blockquote> <p>This will be the central topic in the next section - Solver section.</p> </blockquote> <h3 id="22-solver">2.2 Solver</h3> <p>Now let’s assume we’ve determined \(\vec{f}, \vec{\theta}\) from expert knowledge and/or data, so our score function \(s(y_1...y_n, x_1...x_n)\) is finalized.</p> <p>The job of the solver here is use the score function to efficiently get the best fitting tag sequence \(y_1^{*}...y_n^{*}\) for a given sentence \(x_1...x_n\). It’s an optimization problem:</p> \[y_1^{*}...y_n^{*} = \text{arg} \max_{y_1...y_n} s(y_1...y_n, x_1...x_n)\] <p>We can definitely solve it by brutal force - it’s just that there’s \(5^7\) possible \(y_1...y_n\) sequences in the example below, meaning we have to evaluate \(5^7\) times of our score function before getting the optimal sequence.</p> <p class="srs_img"><img src="/assets/ner_post/img/solver-1.png" alt="Alt text" width="80%"/></p> <p>The computation scales badly with the length of sentence.</p> <blockquote> <p>The computation complexity is exponential - \(O(5^N)\) where \(N\) is the length of sentence.</p> </blockquote> <p>It turns out the score function exhibits a special mathematic structure that enables us to solve the optimization problem in linear time \(O(N)\). Let’s talk about that.</p> <h4 id="221-viterbi-algorithm">2.2.1 Viterbi Algorithm</h4> <p>We notice our score function is a product of successive conditional probabilities \(p(y_i |y_{i-1}, x_1...x_n)\) as below:</p> \[s(y_1...y_n, x_1...x_n) = \prod_{i=1}^{n} p(y_i |y_{i-1}, x_1...x_n)\] <blockquote> <p>You’ll see this is a very good property that allows us to optimize step by step via dynamic programming (in this case we call Viterbi Algorithm). So, bear with me for a bit…</p> </blockquote> <p>Because of this nice and clean form, we can easily define a partial score function \(s_{partial}\) - score of the first \(k\) tags \(y_1...y_k\) for the same given sentence \(x_1...x_n\).</p> \[s_{partial, k}(y_1...y_k, x_1...x_n) = \prod_{i=1}^{k} p(y_i |y_{i-1}, x_1...x_n)\] <p>When \(k=n\), the partial score function becomes the original score function.</p> <p class="srs_img"><img src="/assets/ner_post/img/solver-2.png" alt="Alt text" width="80%"/></p> <p>We can also easily find the <strong>recursive form</strong> of the partial score function:</p> \[s_{partial, k}(y_1...y_k, x_1...x_n) = p(y_k |y_{k-1}, x_1...x_n) \cdot s_{partial, k-1}(y_1...y_{k-1}, x_1...x_n)\] <p>Now, we only need answer <strong>three</strong> questions to explain the algorithm.</p> <ul> <li>How would you find highest partial score where \(k=1\), namely \(s_{partial, 1}(y_1^{*}, x_1...x_n)\)?</li> </ul> <blockquote> <p>This is easy because it’s just a single-variable optimization and \(y_1\) can only have 5 choices.</p> </blockquote> <p>We can just evaluate \(s_{partial, 1}(y_1, x_1...x_n)\) for 5 times and pick the highest one. To help answer the following questions, we’ll save the 5 evaluated numbers. Here we carried out \(5\) evalutions to get \(s_{partial, 1}(y_1^{*}, x_1...x_n)\).</p> <ul> <li>How would you find highest partial score where \(k=2\), namely \(s_{partial, 2}(y_1^{*}, y_2^{*}, x_1...x_n)\)?</li> </ul> <blockquote> <p>It’s just bi-variable optimization. We can first keep \(y_2\) fixed and optimize over \(y_1\) dimension.</p> </blockquote> <p>We can calculate the best \(s_{partial, 2}(y_1^{*},y_2=\text{PER}, x_1...x_n)\) by evaluating the following 5 products and pick the best one.</p> <ol> <li> \[p(y_2=\text{PER} |y_1=\text{PER}, x_1...x_n) \cdot s_{partial, 1}(y_1=\text{PER}, x_1...x_n)\] </li> <li> \[p(y_2=\text{PER} |y_1=\text{ORG}, x_1...x_n) \cdot s_{partial, 1}(y_1=\text{ORG}, x_1...x_n)\] </li> <li> \[p(y_2=\text{PER} |y_1=\text{LOC}, x_1...x_n) \cdot s_{partial, 1}(y_1=\text{LOC}, x_1...x_n)\] </li> <li> \[p(y_2=\text{PER} |y_1=\text{MISC}, x_1...x_n) \cdot s_{partial, 1}(y_1=\text{MISC}, x_1...x_n)\] </li> <li> \[p(y_2=\text{PER} |y_1=\text{O}, x_1...x_n) \cdot s_{partial, 1}(y_1=\text{O}, x_1...x_n)\] </li> </ol> <blockquote> <p>Note we can reuse \(s_{partial, 1}(y_1, x_1...x_n), y_1 \in \{\text{PER, ORG, LOC, MISC, O}\}\) saved from previous question.</p> </blockquote> <p>In this manner, we have all of \(s_{partial, 2}(y_1^{*},y_2, x_1...x_n), y_2 \in \{\text{PER, ORG, LOC, MISC, O}\}\) ready and the highest of the five would be the answer. Here we carried out \(5*5=25\) evalutions to get \(s_{partial, 2}(y_1^{*}, y_2^{*}, x_1...x_n)\).</p> <ul> <li>How about \(k=3\) and beyond?</li> </ul> <p>At this moment, you might have noticed that we could get \(s_{partial, 3}(y_1^{*}, y_2^{*}, y_3^{*}, x_1...x_n)\) by evaluating \(s_{partial, 3}(y_1^{*},y_2^{*}, y_3, x_1...x_n), y_3 \in \{\text{PER, ORG, LOC, MISC, O}\}\) and picking the highest of the five.</p> <p>Each \(s_{partial, 3}(y_1^{*},y_2^{*}, y_3=u, x_1...x_n)\) can be easily calculated via</p> \[\max_{y_2} p(y_3=u |y_2, x_1...x_n) \cdot s_{partial, 2}(y_1^{*}, y_2, x_1...x_n)\] <blockquote> <p>Note we can reuse \(s_{partial, 2}(y_1^{*}, y_2, x_1...x_n), y_2 \in \{\text{PER, ORG, LOC, MISC, O}\}\) saved from previous question.</p> </blockquote> <p>Similarly, it takes another 25 evalutions. Basically we can keep rolling to all the way \(k=n\) with each step forward we carry out another 25 evaluations. So the total compuation complexity is around \(25N\), namely \(O(N)\).</p> <p>This post turns out to be longer than I thought, I was only able to answer the first questions raised in the beginning. And this seemingly natural approach is actually the so-called <em>maximum entropy Markov model</em> (MEMM) approach. There’s many more other ways to solve NER problem, which I’ll talk about in next posts together with the answers to the remaining three questions. See you soon.</p>]]></content><author><name></name></author><category term="nlp,"/><category term="explained"/><summary type="html"><![CDATA[Recently I’ve been working on a project related to Named Entity Recognition (NER). At the very beginning, I was trying to find a well-explained document to get myself started, but couldn’t do so (instead I found redundant pieces here and there on the Internet). My requirement is simple. It should include]]></summary></entry><entry><title type="html">Molecular ConvNet in Property Prediction</title><link href="https://kehang.github.io/blog/2017/machine-learning-in-molecular-property-prediction/" rel="alternate" type="text/html" title="Molecular ConvNet in Property Prediction"/><published>2017-04-18T01:01:11+00:00</published><updated>2017-04-18T01:01:11+00:00</updated><id>https://kehang.github.io/blog/2017/machine-learning-in-molecular-property-prediction</id><content type="html" xml:base="https://kehang.github.io/blog/2017/machine-learning-in-molecular-property-prediction/"><![CDATA[<p>In chemistry discovery, we try to explore <strong>vast, unknown chemical space</strong> (molecules and reactions) and discover most essential chemistry for specific chemical systems. By doing that, we are able to <strong>purposefully create</strong> valuable applications such as optimizing fuel-additive ratio for engine combustion, new drug discovery for certain types of dieseas, etc. In order to <strong>automate</strong> the discovery process, we’ve developed an open source project <a href="https://github.com/ReactionMechanismGenerator/RMG-Py">RMG-Py</a>, of which some useful tools are available in <a href="http://rmg.mit.edu/">RMG-website</a>.</p> <p class="srs_img"><img src="/assets/molconv_post/img/HXD13flux-small.png" alt="Alt text" width="80%"/></p> <p>In that process, one of the most challenging problems is to <strong>predict thermodynamic properties</strong> (e.g., enthalpy, entropy, heat capacities) for <strong>any</strong> molecules in the chemical space, of which most we may have <strong>never seen before</strong>.</p> <p>In machine learning language, molecular prediction research is essentially answer how to smartly <strong>map</strong> a molecule, regarded as <strong>a node-edge graph</strong>, to <strong>a feature vector</strong> and then design appropriate regression model to get to molecular property.</p> <p>This post reviews several traditional approaches (used as base-line models) and propose a new solution using <strong>Molecular Convolutional Neural Networks</strong>, which boosts prediction performance remarkably.</p> <h3 id="0-datasets">0. Datasets</h3> <p>The datasets we are using are crafted originally from a large quantum-mechanics calculation dataset named <a href="https://www.nature.com/articles/sdata201422">ScientificData-134k</a> (thereafter <code class="language-plaintext highlighter-rouge">sdata134k</code>) by Ramakrishnan. Since the most difficult-to-predict molecules always involves cyclic structures, this post gathers all cyclic molecules from sdata134k and categorizes them into into <strong>hydrocarbon cyclics</strong> and <strong>oxygen-contained cyclics</strong>.</p> <h4 id="dataset-1-sdata134k-hydrocarbon-cyclic-examples">Dataset 1: sdata134k-hydrocarbon-cyclic examples</h4> <p class="srs_img"><img src="/assets/molconv_post/img/dataset_example.png" alt="Alt text" height="250px" width="280px"/></p> <h4 id="dataset-2-sdata134k-oxygenate-cyclic-examples">Dataset 2: sdata134k-oxygenate-cyclic examples</h4> <p class="srs_img"><img src="/assets/molconv_post/img/oxy_dataset_example.png" alt="Alt text" height="250px" width="280px"/></p> <h3 id="1-base-line-models">1. Base-line models</h3> <p>The traditional approaches that will be covered and used as base-line models includes</p> <ul> <li> <p>Linear model: Group additivity method</p> </li> <li> <p>Molecular fingerprint with neural netoworks</p> </li> </ul> <h4 id="base-line-model-1-group-addtivity-method">Base-line model 1: Group addtivity method</h4> <p>Historically, Benson Group Additivity Method is polularly used because of its simplicity. It breaks down a molecule into sub-structural pieces and sums up their contributions to overall thermodynamic properties, which essentially is a <strong>linear model</strong> with <code class="language-plaintext highlighter-rouge">bag of sub-structures</code>.</p> <p>For example, the molecule 2-methylnonane consists of three types of sub-structures includes:</p> <p class="srs_img"><img src="/assets/molconv_post/img/gav_demo.png" alt="Alt text" width="80%"/></p> <ul> <li>1 tertiary carbon atom</li> <li>6 secondary carbon atoms</li> <li>3 primary carbon atoms</li> </ul> <p>This simple idea is effective for linear-shaped molecules, but for the cyclic molecules collected in the above two datasets, Benson Group Additivity approach (already implemented in <a href="http://rmg.mit.edu/molecule_search">RMG thermo estimator</a>) performs badly:</p> <ul> <li>Enthalpy energy prediction <code class="language-plaintext highlighter-rouge">test error = 60 kcal/mol</code> (RMSE).</li> </ul> <p>The error largely comes from its underlying design/assumption:</p> <ul> <li>the <strong>effect of relative positions</strong> of sub-structures is negiligle (<strong>independent contributions</strong> from sub-structures)</li> <li>sub-structures are very small, usually atom based</li> </ul> <p>The assumptions become less valid when it comes to cyclic compounds since they are relatively large molecules with strong interations between sub-structures.</p> <h4 id="base-line-model-2-morgan-fingerprint-with-neural-net">Base-line model 2: Morgan fingerprint with neural net</h4> <p>Morgan fingerprint, also known as ECFP (<a href="https://docs.chemaxon.com/display/docs/Extended+Connectivity+Fingerprint+ECFP">Extended-Connectivity Fingerprint</a>) tries to see bigger chemical environments within a moleucle. It defines fragments with user-specified radius (e.g., 2-atom distance radius). More details can be found in <a href="http://www.rdkit.org/UGM/2012/Landrum_RDKit_UGM.Fingerprints.Final.pptx.pdf">RDKit presentation</a>.</p> <p>In this base-line model, we use Morgan fingerprint as our molecular feature vector. We also use one-hidden-layer neural networks to account for non-linear interations between fragments.</p> <p>The architecture is shown below:</p> <p class="srs_img"><img src="/assets/molconv_post/img/morgan_demo.png" alt="Alt text" width="80%"/></p> <p>This approach of combining Morgan fingerprint and non-linear regression model improves prediction performance:</p> <ul> <li>Enthalpy energy prediction <code class="language-plaintext highlighter-rouge">test error = 19 kcal/mol</code> (RMSE).</li> </ul> <p>However, 19 kcal/mol is still not satisfying in chemistry discovery; to our experience, enthalpy energy uncertainty of larger than 5 kcal/mol could easily mis-guide discovery direction.</p> <h3 id="2-molecular-convolutional-neural-network">2. Molecular convolutional neural network</h3> <p>Besides accuracy issue, for fixed molecular fingerprint, people face the practical challenge that feature engineering requires chemical expertise and manual tweaking, naturally slowing down the process of learning from data.</p> <p>To solve that, one idea by <a href="https://arxiv.org/abs/1509.09292">Aspuru-Guzik</a> is to make fingerprint also learnable from data. Thus, this project is actually to build an accurate enthalpy estimator that learns featurization and regression altogether from data.</p> <p>More specifically, the learnable featurization module uses graph convolutional neural networks (see architecture below). The learned feature vector willthen be fed into a fully connected neural network with one hidden layer, which serves the regression model.</p> <h4 id="architecture"><strong>Architecture</strong></h4> <p class="srs_img"><img src="/assets/molconv_post/img/molconv_demo.png" alt="Alt text" width="80%"/></p> <p>Each input molecule to molecular convolutional neural network (thereafter MCNN) is represented by three components: atom fingerprint matrix (denoted by A), bond fingerprint tensor (denoted by B), connectivity matrix (denoted by C).</p> <p>For dimensionality, \(A\) is \(n_a × f_a\), \(B\) is \(n_a × n_a × f_b\), and \(C\) is \(n_a × n_a\), where \(n_a\) is the number of atoms, \(f_a\) the size of atom fingerprint and \(f_b\) the size of bond fingerprint. The output of MCNN is molecular fingerprint vector with size of \(f_m\).</p> <p><em><strong>Atom fingerprint matrix \(A\)</strong></em></p> <p>For a given molecule, \(A\) stores the information at atom level; each atom has a fingerprint vector therefore \(A\) has size of \(n_a × f_a\). Atom fingerprint includes basic atomic information such as atomic charge, number of attached hydrogens, etc.</p> <p><em><strong>Bond fingerprint matrix \(B\)</strong></em></p> <p>For a given molecule, \(B\) stores the information at bond level; each bond has a fingerprint vector therefore \(B\) has size of na × na × fb. Bond fingerprint includes basic information such as bond order, whether the bond is in ring, etc.</p> <p><em><strong>Connectivity matrix \(C\)</strong></em></p> <p>For a given molecule, each possible pair of atoms has an entry in \(C\); 1 indicates there’s a bond between the pair, vice versa, therefore \(C\) has size of \(n_a × n_a\).</p> <p><em><strong>Molecular convolution</strong></em></p> <p>We could add each atom fingerprint to its local neighbors by some weights if neccessary, called molecular convolution, using \(A\) and \(C\), and get convoluted atom fingerprint matrix \(A^{r=1}\). r = 1 indicates the output fingerprint contains local information with radius of 1 atom distance. Increasing radius by consecutive convolution gives \(A^{r=m}\) which captures larger local neighborhood information.</p> <h4 id="performance"><strong>Performance</strong></h4> <p>The best model selected from 5-fold cross-validation gives remarkable preformance boost:</p> <ul> <li>Enthalpy energy prediction <code class="language-plaintext highlighter-rouge">test error = 3 kcal/mol</code> (RMSE).</li> </ul> <h4 id="interpretation"><strong>Interpretation</strong></h4> <p>Let’s see if the learned fingerprint makes sense.</p> <p><em>Check 1: <strong>similar molecules with similar fingerprint</strong></em></p> <p class="srs_img"><img src="/assets/molconv_post/img/interpretation_check1.png" alt="Alt text" width="80%"/></p> <p>Three similar cyclic hydrocarbon (first three in the figure) are selected, whose fingerprints share major high peaks. A linear molecule with same number of carbons appears relatively different, as expected. So, pass!</p> <p><em>Check 2: <strong>king-queen =? man-woman</strong></em></p> <p>In <strong>word2vec</strong> project, researchers tends to use subtraction to interpret embedding vector (word equivalent of fingerprint). One of the most famous examples is using <strong>word2vec</strong>, <strong>vector(king)-vector(queen)</strong> is very similar to <strong>vector(man)-vector(woman)</strong>, as semantics requires.</p> <p>Similarly, four molecules are selected:</p> <p class="srs_img"><img src="/assets/molconv_post/img/interpretation_check2.png" alt="Alt text" width="80%"/></p> <p>Again, the <strong>king molecule</strong> and <strong>queen molecule</strong> differ by a 4-member ring, same as for the difference between <strong>man molecule</strong> and <strong>woman molecule</strong>. The two fingerpring differences are quite similar, sharing most major high peaks. Another pass!</p> <p><em>Check 3: <strong>nearest neighbor in fingerprint space</strong></em></p> <p>Although there’s no absolute definition of molecular similarity, human can generally tell if two molecular structures are alike. This check is to see if the machine generates sensible similar molecules in the training set given any molecules.</p> <p class="srs_img"><img src="/assets/molconv_post/img/interpretation_check3.png" alt="Alt text" width="80%"/></p> <p>The given molecule example in the figure consists of a <code class="language-plaintext highlighter-rouge">4-member</code> ring and <code class="language-plaintext highlighter-rouge">6-member</code> ring; since 6-member and 5-member are quite similar (both have similar ring strain contributions), we see most returned neighbours have a <code class="language-plaintext highlighter-rouge">4-member</code> ring and either one <code class="language-plaintext highlighter-rouge">5-member</code> or one <code class="language-plaintext highlighter-rouge">6-member</code> ring. Looking good!</p> <p>Another aspect is unsaturated bonds in the given molecule: one double bond next to the fused bridge and one far-away triple bond. The second neighbour from left is exactly matching this aspect and all the remaining neighbours also have two similar unsaturated parts in structure. Pass!</p> <p>At this point, we’ve kind of got a new model/estimator that appears to have greatly better accuracy and generate sensible fingerprints (although we still don’t exactly know what each entry in the fingerprint vector stands for).</p> <h3 id="3-effectiveness-of-learning-new-data">3. Effectiveness of learning new data</h3> <p>Besides accuracy and interpretability, it’s also crucial to evaluate how effectively the new machine architecture learns from new data.</p> <p>It is because the range of applications RMG explores keeps extending; along the past 10 years, RMG started from modeling nature gas combustion (usually small simple molecules) to recently being capable of handling full chemistry of hydrocarbon up to 20 carbons (obviously a lot new molecular structures come into our scope). On the other hand, it’s extending its capability towards oxygenates, sulfurides and nitrogen compounds as well.</p> <p>So, here we prepared two extra small datasets, whose molecules are distinct from those in the above training datasets.</p> <ul> <li> <p>Large tricyclic: larger rings that those in previous training datasets but mostly have saturated bonds as previous training examples do</p> </li> <li> <p>Unsaturated cyclics: cyclics that have multiple unsaturated bonds in same ring, which is very distinct from preivous training examples.</p> </li> </ul> <p class="srs_img"><img src="/assets/molconv_post/img/effectiveness.png" alt="Alt text" width="70%"/></p> <p>As expected, the unsaturated cyclic dataset starts with higher test error than large tricyclic one, which is because its examples are much more different from previous training examples than the other.</p> <p>Good news is both datasets have test error go down as feeding more new relavent data points. After 50 new data points, the test error enters 10 kcal/mol region.</p> <h3 id="4-future-work">4. Future work</h3> <p>The new proposed estimator appears much effective in predicting molecular themodynamic properties by mapping graph structures through fingerprints to eventually properties. To expand its predicting power to new categories of molecules, we’d make sure it can learn as long as new data is available. But data is a must to make this estimator evolve. To accomplish that, I’ll talk about data insfrastructure construction in next posts to build a data-machine pipeline.</p>]]></content><author><name></name></author><category term="chemistry"/><summary type="html"><![CDATA[In chemistry discovery, we try to explore vast, unknown chemical space (molecules and reactions) and discover most essential chemistry for specific chemical systems. By doing that, we are able to purposefully create valuable applications such as optimizing fuel-additive ratio for engine combustion, new drug discovery for certain types of dieseas, etc. In order to automate the discovery process, we’ve developed an open source project RMG-Py, of which some useful tools are available in RMG-website.]]></summary></entry><entry><title type="html">6.036 Project 2: MNIST Classifiers</title><link href="https://kehang.github.io/blog/2017/mnist-classifiers-exploration/" rel="alternate" type="text/html" title="6.036 Project 2: MNIST Classifiers"/><published>2017-04-02T01:01:11+00:00</published><updated>2017-04-02T01:01:11+00:00</updated><id>https://kehang.github.io/blog/2017/mnist-classifiers-exploration</id><content type="html" xml:base="https://kehang.github.io/blog/2017/mnist-classifiers-exploration/"><![CDATA[<p>This project is about digit classification using the <a href="http://yann.lecun.com/exdb/mnist/">MNIST database</a>. It contains 60,000 training digits and 10,000 testing digits. The goal is to practically explore differenet classifiers and evaluate their performances. The exploration ranges from simplest classifier, e.g.,linear regression with softmax for classification, to deep nerual networks.</p> <p>This report will be available online after due date at: <code class="language-plaintext highlighter-rouge">http://kehang.github.io/</code></p> <p>for people (including graders) to review. After grading, the post will be modified to be a standalone report with more background.</p> <p>Package specifications for this project:</p> <ul> <li> <p>OS: Mac</p> </li> <li> <p>python: 3.6.0</p> </li> <li> <p>keras: 2.0.2</p> </li> <li> <p>backend of keras: theano</p> </li> <li> <p>theano: 0.9.0</p> </li> </ul> <h3 id="1-multinomialsoftmax-regression">1. Multinomial/Softmax Regression</h3> <h4 id="part-4-report-base-line-test-error">Part 4: report base-line test error</h4> <p>when <code class="language-plaintext highlighter-rouge">temperature parameter = 1</code>, the test error is 0.1005, implying the linear softmax regression model is able to recognize MNIST digits with around 90%.</p> <h4 id="part-5-explain-temperate-parameter-effects">Part 5: explain temperate parameter effects</h4> <p>Increasing temperature parameter would decrease the probability of a sample \(x^{(i)}\) being assigned a label that has a large \(\theta\), and increase for labels with small \(\theta\). The mathematic explanation is following:</p> \[P_j = \frac{exp(\theta_j x / \tau)}{\sum_k exp(\theta_k x / \tau)}\] \[\frac{\partial log(P_j)}{\partial \tau} = \frac{1}{\tau^2} \Big[ \frac{\sum_k exp(\theta_k x / \tau) \theta_k x}{\sum_k exp(\theta_k x / \tau)} - \theta_j x \Big]\] <p>The first term is the bracket is weighted average of \(\theta x\), so if \(\theta_j x\) is large, the value of the brackect will be negative, leading to negative \(\frac{\partial log(P_j)}{\partial \tau}\).</p> <p>For small \(\theta_j\), \(\theta_j x\) is also small, we have positive \(\frac{\partial log(P_j)}{\partial \tau}\).</p> <h4 id="part-6-temperate-parameter-effect-on-test-error">Part 6: temperate parameter effect on test error</h4> <p>During experimentation, we found the test error increases with temperature parameter as follows:</p> <p>when <code class="language-plaintext highlighter-rouge">temperature parameter = 0.5</code>, the test error is 0.084</p> <p>when <code class="language-plaintext highlighter-rouge">temperature parameter = 1.0</code>, the test error is 0.1005</p> <p>when <code class="language-plaintext highlighter-rouge">temperature parameter = 2.0</code>, the test error is 0.1261</p> <p>Since from Part 5 we know increasing temperature parameter makes probability of large-\(\theta\) label decrease and that of small-\(\theta\) label increase, the probability distribution becomes <strong>more uniform</strong> as temperature parameter icreases.</p> <h4 id="part-7-classify-digits-by-their-mod-3-values">Part 7: classify digits by their mod 3 values</h4> <p>Note: in this part we use <code class="language-plaintext highlighter-rouge">temperature parameter = 1.0</code>.</p> <p>The error rate of the new labels (mod 3) <code class="language-plaintext highlighter-rouge">testErrorMod3 = 0.0768</code>.</p> <p>The error rate of the original labels <code class="language-plaintext highlighter-rouge">testError = 0.1005</code>.</p> <p>The classification error decreases because those examples being classified correctly with original labels will continue being classified correctly with new labels, but those being classified wrongly originally will have a chance to be classified correctly with new labels.</p> <h4 id="part-8-re-train-and-classify-digits-by-their-mod-3-values">Part 8: re-train and classify digits by their mod 3 values</h4> <p>After re-training using the new labels (mod 3), <code class="language-plaintext highlighter-rouge">testErrorMod3 = 0.1872</code>.</p> <h4 id="part-9-explain-test-error-difference-between-part-7-and-8">Part 9: explain test error difference between Part 7 and 8</h4> <p>Compared with <code class="language-plaintext highlighter-rouge">testErrorMod3 = 0.0768</code> in Part 7, re-training makes the test accuracy worse. It is because now in training step, the algorithm has <strong>less information</strong> (very different digits may have same mod 3 labels) with new labels compared with training using 0-9 digit labels.</p> <h3 id="2-classification-using-manually-crafted-features">2. Classification using manually-crafted features</h3> <p>This section we explore one dimensionality reduction approach: PCA and one dimensionality increase approach: polynomial (specifically <code class="language-plaintext highlighter-rouge">cubic</code> version) feature mapping, to see their effects on test error rates.</p> <h4 id="part-3-report-pca-test-error">Part 3: report PCA test error</h4> <p>When we use dimensionality reduction by applying PCA with 18 principal components, the <code class="language-plaintext highlighter-rouge">test error = 0.1483</code>. This error rate is very similar to the original <code class="language-plaintext highlighter-rouge">d=784</code> case. It is because PCA ensures these 18 feature values capture the maximal amount of variation in the original 784-denmensional data.</p> <h4 id="part-4-first-2-principal-components-visualization">Part 4: first 2 principal components visualization</h4> <p>Below is the visualization of the first 2 pricipal components of 100 training data points.</p> <p class="mnist_img"><img src="/assets/mnist_post/img/plotPC_P2p4.png" alt="Alt text"/></p> <h4 id="part-5-image-reconstruction-from-pca-representations">Part 5: image reconstruction from PCA-representations</h4> <p>Below are the reconstructions of the first two MNIST images fron their 18-dimensional PCA-representations alongside the originals.</p> <p><strong>MNIST 1st Image</strong> (left: reconstructed, right: original)</p> <p><img src="/assets/mnist_post/img/image_x_first_img_recon.png" alt="Alt text" height="300px" width="360px"/> <img src="/assets/mnist_post/img/image_x_first_img.png" alt="Alt text" height="300px" width="360px"/></p> <p><strong>MNIST 2nd Image</strong> (left: reconstructed, right: original)</p> <p><img src="/assets/mnist_post/img/image_x_second_img_recon.png" alt="Alt text" height="300px" width="360px"/> <img src="/assets/mnist_post/img/image_x_second_img.png" alt="Alt text" height="300px" width="360px"/></p> <h4 id="part-6-explicit-cubic-feature-mapping-for-2-dimensional-case">Part 6: explicit cubic feature mapping for 2-dimensional case</h4> <p>For \(x = [x_1, x_2]\) and \(\phi(x)^T \phi(x') = (x^T x' + 1)^3\), we expand and get</p> \[\phi(x)^T \phi(x') = x_1^3{x_1'}^3 + x_2^3{x_2'}^3 + 3 x_1^2{x_1'}^2 x_2 {x_2'} + 3 x_1{x_1'} x_2^2 {x_2'}^2\] \[+ 3 x_1^2{x_1'}^2 + 3 x_2^2{x_2'}^2 + 6 x_1{x_1'} x_2 {x_2'} + 3 x_1 {x_1'} + 3 x_2{x_2'} + 1\] <p>So the corresponding feature vector is</p> \[\phi(x) = (x_1^3, x_2^3, \sqrt{3} x_1^2 x_2, \sqrt{3} x_1 x_2^2, \sqrt{3} x_1^2, \sqrt{3} x_2^2, \sqrt{6} x_1 x_2, \sqrt{3} x_1, \sqrt{3} x_1, 1)^T\] <h4 id="part-7-re-train-using-cubic-feature-mapping">Part 7: re-train using cubic feature mapping</h4> <p>For practical reason, we apply the feature mapping to 10-dimensional PCA representation instead of original 784-dimensional data.</p> <p>With same settings as base-line case (e.g., temperature parameter = 1), the <code class="language-plaintext highlighter-rouge">test error = 0.0865</code>.</p> <h3 id="3-basic-neural-network">3. Basic Neural Network</h3> <h4 id="part-4-improve-learning-rate">Part 4: improve learning rate</h4> <p>Since currently we have constant learning rate, which is not best when approaching the minimum, one way we can do is to reduce learning rate gradually by expressing it as a function of iterations.</p> <h4 id="part-5-too-many-hidden-units">Part 5: too many hidden units</h4> <p>The danger would be overfitting.</p> <h4 id="part-6-training-and-test-error-evolution-against-epochs">Part 6: training and test error evolution against epochs</h4> <p>For training error, it will continue going down with epochs, while for test error, it will first go down and then go up.</p> <h4 id="part-7-optimize-the-number-of-epochs">Part 7: optimize the number of epochs</h4> <p>One way to do is use portion of training data (maybe 10%) as inner validation data, for each epoch we evaluate the inner validation error, if it starts going up consecutively for say, 10 epochs, we stop the training process.</p> <h3 id="4-deep-neural-networks">4. Deep Neural Networks</h3> <h4 id="part-1-fully-connected-neural-networks">Part 1: fully-connected neural networks</h4> <p><strong>(a)</strong> after 10 epochs, the <code class="language-plaintext highlighter-rouge">test accuracy = 0.9172</code>.</p> <p><strong>(b)</strong> My final model architecture (<code class="language-plaintext highlighter-rouge">mnist_nnet_fc_improved.py</code>) has <strong>6 hidden layers</strong> (each has 512 neurons, activation is <code class="language-plaintext highlighter-rouge">ReLU</code>) and 1 output layer (with 10 neurons, activation is <code class="language-plaintext highlighter-rouge">softmax</code>). Optimizer is <code class="language-plaintext highlighter-rouge">SGD(lr=0.03, momentum=0.65, decay=0.0001)</code>. With this architecture, the <code class="language-plaintext highlighter-rouge">test accuracy = 0.9842</code>.</p> <p>Among all the tweaks I’ve tried, increasing the <strong>number of hidden layers</strong> (from 1 hidden layer to 6), the <strong>number of neurons</strong> (from 128 to 512), the <strong>learning rate</strong> (from 0.001 to 0.03, I also modified momentum and decay) help a lot, boosting the test accuracy from <code class="language-plaintext highlighter-rouge">0.9172 to 0.9842</code>.</p> <p>Changing hidden layer activation from <code class="language-plaintext highlighter-rouge">ReLU</code> to <code class="language-plaintext highlighter-rouge">tanh</code> doesn’t make too much difference, but switching to <code class="language-plaintext highlighter-rouge">signmoid</code> really harms test accuracy. Too high or too low decay in learning rate also can damage the accuracy; I believe current choice of decay of 0.0001 is the sweet spot.</p> <h4 id="part-2-convolutional-neural-networks">Part 2: Convolutional neural networks</h4> <p><strong>(a)</strong> A simplest convolutional neural network was constructed in the following order:</p> <ul> <li> <p>A 2D convolutional layer with 32 filters of size 3x3</p> </li> <li> <p>A ReLU activation</p> </li> <li> <p>A max pooling layer with size 2x2</p> </li> <li> <p>A ReLU activation</p> </li> <li> <p>A 2D convolutional layer with 64 filters of size 3x3</p> </li> <li> <p>A ReLU activation</p> </li> <li> <p>A max pooling layer with size 2x2</p> </li> <li> <p>A flatten layer</p> </li> <li> <p>A fully connected layer with 128 neurons</p> </li> <li> <p>A dropout layer with drop probability 0.5</p> </li> <li> <p>A fully connected layer with 10 neurons</p> </li> <li> <p>A softmax activation</p> </li> </ul> <p>The implementation using <code class="language-plaintext highlighter-rouge">keras</code> is list below:</p> <figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="n">model</span> <span class="o">=</span> <span class="nc">Sequential</span><span class="p">()</span>
<span class="n">model</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="nc">Conv2D</span><span class="p">(</span><span class="n">filters</span><span class="o">=</span><span class="mi">32</span><span class="p">,</span><span class="n">kernel_size</span><span class="o">=</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span><span class="mi">3</span><span class="p">),</span><span class="n">input_shape</span><span class="o">=</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">X_train</span><span class="p">.</span><span class="n">shape</span><span class="p">[</span><span class="mi">2</span><span class="p">],</span> <span class="n">X_train</span><span class="p">.</span><span class="n">shape</span><span class="p">[</span><span class="mi">3</span><span class="p">])))</span>
<span class="n">model</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="nc">Activation</span><span class="p">(</span><span class="sh">"</span><span class="s">relu</span><span class="sh">"</span><span class="p">))</span>
<span class="n">model</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="nc">MaxPooling2D</span><span class="p">(</span><span class="n">pool_size</span><span class="o">=</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">)))</span>

<span class="n">model</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="nc">Conv2D</span><span class="p">(</span><span class="n">filters</span><span class="o">=</span><span class="mi">64</span><span class="p">,</span><span class="n">kernel_size</span><span class="o">=</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span><span class="mi">3</span><span class="p">)))</span>
<span class="n">model</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="nc">Activation</span><span class="p">(</span><span class="sh">"</span><span class="s">relu</span><span class="sh">"</span><span class="p">))</span>
<span class="n">model</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="nc">MaxPooling2D</span><span class="p">(</span><span class="n">pool_size</span><span class="o">=</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">)))</span>

<span class="n">model</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="nc">Flatten</span><span class="p">())</span>
<span class="n">model</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="nc">Dense</span><span class="p">(</span><span class="n">units</span><span class="o">=</span><span class="mi">128</span><span class="p">))</span>
<span class="n">model</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="nc">Dropout</span><span class="p">(</span><span class="n">rate</span><span class="o">=</span><span class="mf">0.5</span><span class="p">))</span>
<span class="n">model</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="nc">Dense</span><span class="p">(</span><span class="n">units</span><span class="o">=</span><span class="mi">10</span><span class="p">))</span>
<span class="n">model</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="nc">Activation</span><span class="p">(</span><span class="sh">"</span><span class="s">softmax</span><span class="sh">"</span><span class="p">))</span></code></pre></figure> <p>The result is quite encouraging, just one epoch, the <code class="language-plaintext highlighter-rouge">test accuracy</code> reaches <code class="language-plaintext highlighter-rouge">0.9810</code>. But it does take longer to finish one epoch (48 secs, while 4 secs for the Part 1 fully-connected neural networks).</p> <p>To speedup the training time and achieve higher <code class="language-plaintext highlighter-rouge">test accuracy</code>, I set up <a href="/blog/2017/install-CUDA-cuDNN-on-Red-Hat/">CUDA drivers in a accessible GPU</a> and ran the same ConvNN for <code class="language-plaintext highlighter-rouge">15 epochs</code>. Here’s the training log from GPU:</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">Using Theano backend.
Using cuDNN version 5110 on context None
Mapped name None to device cuda: Quadro K2200 <span class="o">(</span>0000:03:00.0<span class="o">)</span>
Train on 54000 samples, validate on 6000 samples
Epoch 1/15
54000/54000 <span class="o">[================]</span> - 6s - loss: 0.2062 - acc: 0.9346 - val_loss: 0.0555 - val_acc: 0.9822
Epoch 2/15
54000/54000 <span class="o">[================]</span> - 6s - loss: 0.0771 - acc: 0.9771 - val_loss: 0.0408 - val_acc: 0.9878
Epoch 3/15
54000/54000 <span class="o">[================]</span> - 6s - loss: 0.0577 - acc: 0.9820 - val_loss: 0.0351 - val_acc: 0.9887
......
Epoch 12/15
54000/54000 <span class="o">[================]</span> - 6s - loss: 0.0193 - acc: 0.9934 - val_loss: 0.0346 - val_acc: 0.9912
Epoch 13/15
54000/54000 <span class="o">[================]</span> - 6s - loss: 0.0174 - acc: 0.9944 - val_loss: 0.0317 - val_acc: 0.9912
Epoch 14/15
54000/54000 <span class="o">[================]</span> - 6s - loss: 0.0157 - acc: 0.9945 - val_loss: 0.0345 - val_acc: 0.9912
Epoch 15/15
54000/54000 <span class="o">[================]</span> - 6s - loss: 0.0151 - acc: 0.9948 - val_loss: 0.0348 - val_acc: 0.9905
8832/10000 <span class="o">[===========&gt;</span>....] - ETA: 0sLoss on <span class="nb">test set</span>:0.0324026019065 Accuracy on <span class="nb">test set</span>: 0.9902</code></pre></figure> <p>It achieves <code class="language-plaintext highlighter-rouge">99.02%</code> of test accuracy after 15 epochs, and each epoch takes only <code class="language-plaintext highlighter-rouge">6 secs</code>.</p> <h3 id="5-classification-for-overlapping-multi-digit-mnist">5. Classification for overlapping multi-digit MNIST</h3> <h4 id="pre-step-data-representation-and-model-arguments">Pre-step: data representation and model arguments</h4> <p>Training set has 40,000 examples, each example is 42x28 size image, which has two labels indicating two overlapping digits in the image. Each label is a vector of length 10.</p> <p>Testing set has 4,000 examples, each example is also 42x28 size image, which also has two labels indicating two overlapping digits in the image. Each label is a vector of length 10.</p> <p><code class="language-plaintext highlighter-rouge">y_train[0]</code> is the fisrt digits in the training images, while <code class="language-plaintext highlighter-rouge">y_train[1]</code> is the second.</p> <p>For <code class="language-plaintext highlighter-rouge">model.compile</code>, <code class="language-plaintext highlighter-rouge">loss='categorical_crossentropy'</code> means the loss function type is cross entropy function for both outputs, <code class="language-plaintext highlighter-rouge">optimizer='sgd'</code> means algorithm is using stochastic gradient descent method to learn, <code class="language-plaintext highlighter-rouge">metrics=['accuracy']</code> means accuracy is used as metric, and <code class="language-plaintext highlighter-rouge">loss_weights=[0.5, 0.5]</code> means the two labels are equally treated when calculating loss.</p> <p>For <code class="language-plaintext highlighter-rouge">model.fit</code>, <code class="language-plaintext highlighter-rouge">X_train, [y_train[0], y_train[1]]</code> are the training data and their labels, <code class="language-plaintext highlighter-rouge">epochs=nb_epoch</code> means total epochs for training, <code class="language-plaintext highlighter-rouge">batch_size=batch_size</code> means training is using mini-batch gradient descent approach, and <code class="language-plaintext highlighter-rouge">verbose=1</code> is setting the logging verbosity.</p> <h4 id="five-explored-models">Five explored models</h4> <p><strong>model1: mlp</strong> (coded in <code class="language-plaintext highlighter-rouge">mlp.py</code>)</p> <p>The architecture is shown below, with one hidden layer of 64 neurons (<code class="language-plaintext highlighter-rouge">relu</code> activation).</p> <p class="srs_img"><img src="/assets/mnist_post/img/mlp.png" alt="Alt text" height="250px" width="230px"/></p> <p>After training 30 epochs, the test accuracy for <code class="language-plaintext highlighter-rouge">1st label</code> is <code class="language-plaintext highlighter-rouge">0.9378</code>, for <code class="language-plaintext highlighter-rouge">2nd label</code> is <code class="language-plaintext highlighter-rouge">0.9278</code>. Total training time is 3 secs/epoch, total 90 secs (run on NVIDIA Quadro K2200 GPU, same for all the other four models).</p> <p><strong>model2: mlp2</strong> (coded in <code class="language-plaintext highlighter-rouge">mlp2.py</code>)</p> <p>The architecture is shown below, with <code class="language-plaintext highlighter-rouge">6 hidden layer</code> of 64 neurons (<code class="language-plaintext highlighter-rouge">relu</code> activation).</p> <p class="srs_img"><img src="/assets/mnist_post/img/mlp2.png" alt="Alt text" height="420px" width="180px"/></p> <p>After training 30 epochs, the test accuracy for <code class="language-plaintext highlighter-rouge">1st label</code> is <code class="language-plaintext highlighter-rouge">0.9503</code>, for <code class="language-plaintext highlighter-rouge">2nd label</code> is <code class="language-plaintext highlighter-rouge">0.9429</code>. Total training time is 4 secs/epoch, total 120 secs.</p> <p><strong>model3: conv</strong> (coded in <code class="language-plaintext highlighter-rouge">conv.py</code>)</p> <p>The architecture is shown below. The first <code class="language-plaintext highlighter-rouge">Conv2D</code> has 8 filters, and second has 16 filters. <code class="language-plaintext highlighter-rouge">SGD</code> is the optimizer.</p> <p class="srs_img"><img src="/assets/mnist_post/img/conv.png" alt="Alt text" height="420px" width="200px"/></p> <p>After training 15 epochs, the test accuracy for <code class="language-plaintext highlighter-rouge">1st label</code> is <code class="language-plaintext highlighter-rouge">0.9828</code>, for <code class="language-plaintext highlighter-rouge">2nd label</code> is <code class="language-plaintext highlighter-rouge">0.9755</code>. Total training time is 14 secs/epoch, total 210 secs.</p> <p><strong>model4: conv2</strong> (coded in <code class="language-plaintext highlighter-rouge">conv2.py</code>)</p> <p>The architecture is exactly same as model <code class="language-plaintext highlighter-rouge">conv</code>. The difference is <code class="language-plaintext highlighter-rouge">adam</code> is the optimizer.</p> <p>After training 15 epochs, the test accuracy for <code class="language-plaintext highlighter-rouge">1st label</code> is <code class="language-plaintext highlighter-rouge">0.9869</code>, for <code class="language-plaintext highlighter-rouge">2nd label</code> is <code class="language-plaintext highlighter-rouge">0.9822</code>. Total training time is 14 secs/epoch, total 210 secs.</p> <p><strong>model5: conv3</strong> (coded in <code class="language-plaintext highlighter-rouge">conv3.py</code>)</p> <p>The architecture is exactly same as model <code class="language-plaintext highlighter-rouge">conv</code>. The difference is that the first <code class="language-plaintext highlighter-rouge">Conv2D</code> has 32 filters, and second has 64 filters</p> <p>After training 15 epochs, the test accuracy for <code class="language-plaintext highlighter-rouge">1st label</code> is <code class="language-plaintext highlighter-rouge">0.9890</code>, for <code class="language-plaintext highlighter-rouge">2nd label</code> is <code class="language-plaintext highlighter-rouge">0.9869</code>. Total training time is 39 secs/epoch, total 585 secs.</p> <h4 id="thoughts-on-model-selection">Thoughts on model selection</h4> <p>It seems that convolutional neural networks are more effective in learning images than fully connected neural networks. Model <code class="language-plaintext highlighter-rouge">mlp</code> and <code class="language-plaintext highlighter-rouge">mlp2</code> can only achieve test accuracy around <code class="language-plaintext highlighter-rouge">0.95</code>, while <code class="language-plaintext highlighter-rouge">conv</code>, <code class="language-plaintext highlighter-rouge">conv2</code> and <code class="language-plaintext highlighter-rouge">conv3</code> can easily achieve beyond <code class="language-plaintext highlighter-rouge">0.98</code>.</p> <p>For fully connected neural networks, adding more hidden layers seems helpful at least from 1 hidden layer to 6 hidden layers.</p> <p>Sometime optimizer can make a difference as well, e.g., here using <code class="language-plaintext highlighter-rouge">adam</code> optimizer is more effective than <code class="language-plaintext highlighter-rouge">SGD</code> to reach minimum.</p>]]></content><author><name></name></author><category term="basic_project"/><summary type="html"><![CDATA[This project is about digit classification using the MNIST database. It contains 60,000 training digits and 10,000 testing digits. The goal is to practically explore differenet classifiers and evaluate their performances. The exploration ranges from simplest classifier, e.g.,linear regression with softmax for classification, to deep nerual networks.]]></summary></entry><entry><title type="html">Install CUDA and cuDNN on Red Hat</title><link href="https://kehang.github.io/blog/2017/install-CUDA-cuDNN-on-Red-Hat/" rel="alternate" type="text/html" title="Install CUDA and cuDNN on Red Hat"/><published>2017-03-31T03:39:51+00:00</published><updated>2017-03-31T03:39:51+00:00</updated><id>https://kehang.github.io/blog/2017/install-CUDA-cuDNN-on-Red-Hat</id><content type="html" xml:base="https://kehang.github.io/blog/2017/install-CUDA-cuDNN-on-Red-Hat/"><![CDATA[<p>I’ve been building neural networks for my chemical space deep learning research since last year. Speedup of training is always one of the central topics. Recently my research group purchased a <code class="language-plaintext highlighter-rouge">Quadro K2200</code> for our Red Hat workstation. I thought it’s a good opportunity of accelerating the computation by switching to GPU. The benefit detail for my research projects will probably be covered in later posts.</p> <p>Today I’m going to focus on how to smoothly install the <strong>two important tools</strong> for any neural network applications to run on GPUs: <strong>CUDA</strong> and <strong>cuDNN</strong>. I noticed there’s serveral installation guides online, e.g., <a href="http://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#axzz4cwRN8XWN">NVIDIA official guide</a>, <a href="http://expressionflow.com/2016/10/09/installing-tensorflow-on-an-aws-ec2-p2-gpu-instance/">AWS EC2 guide 1</a> and <a href="http://www.pyimagesearch.com/2016/07/04/how-to-install-cuda-toolkit-and-cudnn-for-deep-learning/">AWS EC2 guide 2</a>. They either are outdated, missing some key steps or contain unneccessary settings. More importantly for beginners, we want to have some tests to see each major step is gone though correctly and neccessarily.</p> <p>For this purpose I decided to create this post, whose goal is to install CUDA and cuDNN on Red Hat Enterprise Linux 7 in a more <strong>transparent</strong> and <strong>reasonable</strong> way.</p> <p>Just to emphasize, my situation was:</p> <ul> <li> <p>I could easily install <code class="language-plaintext highlighter-rouge">theano</code>/<code class="language-plaintext highlighter-rouge">tensorflow</code>/<code class="language-plaintext highlighter-rouge">keras</code> through <code class="language-plaintext highlighter-rouge">anaconda</code> binary platform,</p> </li> <li> <p>my application can already successfully run on CPUs,</p> </li> <li> <p>I only need to make <code class="language-plaintext highlighter-rouge">theano</code>/<code class="language-plaintext highlighter-rouge">tensorflow</code>/<code class="language-plaintext highlighter-rouge">keras</code> detect there’s GPU available</p> </li> </ul> <h2 id="test-examples-prep">Test Examples Prep</h2> <p>I prepared two python test scripts: <code class="language-plaintext highlighter-rouge">example_1</code> is from <a href="http://deeplearning.net/software/theano/tutorial/using_gpu.html">theano official documentation</a>, which is easy and fast to test whether we have connected to GPU. <code class="language-plaintext highlighter-rouge">example_2</code> is from <a href="https://github.com/fchollet/keras/blob/master/examples/cifar10_cnn.py">keras example cifar10_cnn</a>, which will be used to final check the speedup brought by GPU. Below is the detail of <code class="language-plaintext highlighter-rouge">example_1</code> script.</p> <figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="kn">from</span> <span class="n">theano</span> <span class="kn">import</span> <span class="n">function</span><span class="p">,</span> <span class="n">config</span><span class="p">,</span> <span class="n">shared</span><span class="p">,</span> <span class="n">tensor</span>
<span class="kn">import</span> <span class="n">numpy</span>
<span class="kn">import</span> <span class="n">time</span>

<span class="n">vlen</span> <span class="o">=</span> <span class="mi">10</span> <span class="n">_</span> <span class="mi">30</span> <span class="n">_</span> <span class="mi">768</span> <span class="c1"># 10 x #cores x # threads per core
</span><span class="n">iters</span> <span class="o">=</span> <span class="mi">1000</span>

<span class="n">rng</span> <span class="o">=</span> <span class="n">numpy</span><span class="p">.</span><span class="n">random</span><span class="p">.</span><span class="nc">RandomState</span><span class="p">(</span><span class="mi">22</span><span class="p">)</span>
<span class="n">x</span> <span class="o">=</span> <span class="nf">shared</span><span class="p">(</span><span class="n">numpy</span><span class="p">.</span><span class="nf">asarray</span><span class="p">(</span><span class="n">rng</span><span class="p">.</span><span class="nf">rand</span><span class="p">(</span><span class="n">vlen</span><span class="p">),</span> <span class="n">config</span><span class="p">.</span><span class="n">floatX</span><span class="p">))</span>
<span class="n">f</span> <span class="o">=</span> <span class="nf">function</span><span class="p">([],</span> <span class="n">tensor</span><span class="p">.</span><span class="nf">exp</span><span class="p">(</span><span class="n">x</span><span class="p">))</span>
<span class="nf">print</span><span class="p">(</span><span class="n">f</span><span class="p">.</span><span class="n">maker</span><span class="p">.</span><span class="n">fgraph</span><span class="p">.</span><span class="nf">toposort</span><span class="p">())</span>
<span class="n">t0</span> <span class="o">=</span> <span class="n">time</span><span class="p">.</span><span class="nf">time</span><span class="p">()</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">iters</span><span class="p">):</span>
<span class="n">r</span> <span class="o">=</span> <span class="nf">f</span><span class="p">()</span>
<span class="n">t1</span> <span class="o">=</span> <span class="n">time</span><span class="p">.</span><span class="nf">time</span><span class="p">()</span>
<span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">Looping %d times took %f seconds</span><span class="sh">"</span> <span class="o">%</span> <span class="p">(</span><span class="n">iters</span><span class="p">,</span> <span class="n">t1</span> <span class="o">-</span> <span class="n">t0</span><span class="p">))</span>
<span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">Result is %s</span><span class="sh">"</span> <span class="o">%</span> <span class="p">(</span><span class="n">r</span><span class="p">,))</span>
<span class="k">if</span> <span class="n">numpy</span><span class="p">.</span><span class="nf">any</span><span class="p">([</span><span class="nf">isinstance</span><span class="p">(</span><span class="n">x</span><span class="p">.</span><span class="n">op</span><span class="p">,</span> <span class="n">tensor</span><span class="p">.</span><span class="n">Elemwise</span><span class="p">)</span> <span class="ow">and</span>
<span class="p">(</span><span class="sh">'</span><span class="s">Gpu</span><span class="sh">'</span> <span class="ow">not</span> <span class="ow">in</span> <span class="nf">type</span><span class="p">(</span><span class="n">x</span><span class="p">.</span><span class="n">op</span><span class="p">).</span><span class="n">__name__</span><span class="p">)</span>
<span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">f</span><span class="p">.</span><span class="n">maker</span><span class="p">.</span><span class="n">fgraph</span><span class="p">.</span><span class="nf">toposort</span><span class="p">()]):</span>
<span class="nf">print</span><span class="p">(</span><span class="sh">'</span><span class="s">Used the cpu</span><span class="sh">'</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="nf">print</span><span class="p">(</span><span class="sh">'</span><span class="s">Used the gpu</span><span class="sh">'</span><span class="p">)</span></code></pre></figure> <h2 id="python-anaconda-environment-setup">Python Anaconda Environment Setup</h2> <p>One command can create a conda environment with: <code class="language-plaintext highlighter-rouge">theano</code>. Just run on your terminal:</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span>conda create <span class="nt">-n</span> theano_test <span class="nt">-c</span> conda-forge theano</code></pre></figure> <h1 id="status-of-test-examples-able-to-run-on-cpus-but-not-gpus">Status of test examples: able to run on CPUs but not GPUs</h1> <p>At this point, we’ll run <code class="language-plaintext highlighter-rouge">example_1</code> script (no need to run <code class="language-plaintext highlighter-rouge">example_2</code>) to make sure <code class="language-plaintext highlighter-rouge">example_1</code> can run on CPUs but not on GPUs.</p> <p>Run <code class="language-plaintext highlighter-rouge">example_1</code> on CPU mode:</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="o">(</span>theano_test<span class="o">)</span> <span class="o">[</span>kehang]<span class="nv">$ </span>python example_1.py</code></pre></figure> <p>Result of <code class="language-plaintext highlighter-rouge">example_1</code> for CPU mode:</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="o">[</span>Elemwise<span class="o">{</span>exp,no_inplace<span class="o">}(</span>&lt;TensorType<span class="o">(</span>float64, vector<span class="o">)&gt;)]</span>
Looping 1000 <span class="nb">times </span>took 4.095498 seconds
Result is <span class="o">[</span> 1.23178032 1.61879341 1.52278065 ..., 2.20771815 2.29967753
1.62323285]
Used the cpu</code></pre></figure> <p>Run <code class="language-plaintext highlighter-rouge">example_1</code> on GPU mode:</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="o">(</span>theano_test<span class="o">)</span> <span class="o">[</span>kehang]<span class="nv">$ THEANO_FLAGS</span><span class="o">=</span><span class="nv">device</span><span class="o">=</span>cuda python example_1.py</code></pre></figure> <p>Result of <code class="language-plaintext highlighter-rouge">example_1</code> for GPU mode:</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">ERROR <span class="o">(</span>theano.gpuarray<span class="o">)</span>: Could not initialize pygpu, support disabled
Traceback <span class="o">(</span>most recent call last<span class="o">)</span>:
File <span class="s2">"/home/kehang/miniconda/envs/keras_test/lib/python2.7/site-packages/theano/gpuarray/**init**.py"</span>, line 164, <span class="k">in</span> &lt;module&gt;
use<span class="o">(</span>config.device<span class="o">)</span>
File <span class="s2">"/home/kehang/miniconda/envs/keras_test/lib/python2.7/site-packages/theano/gpuarray/**init**.py"</span>, line 151, <span class="k">in </span>use
init_dev<span class="o">(</span>device<span class="o">)</span>
File <span class="s2">"/home/kehang/miniconda/envs/keras_test/lib/python2.7/site-packages/theano/gpuarray/**init**.py"</span>, line 60, <span class="k">in </span>init_dev
<span class="nv">sched</span><span class="o">=</span>config.gpuarray.sched<span class="o">)</span>
File <span class="s2">"pygpu/gpuarray.pyx"</span>, line 614, <span class="k">in </span>pygpu.gpuarray.init <span class="o">(</span>pygpu/gpuarray.c:9415<span class="o">)</span>
File <span class="s2">"pygpu/gpuarray.pyx"</span>, line 566, <span class="k">in </span>pygpu.gpuarray.pygpu_init <span class="o">(</span>pygpu/gpuarray.c:9106<span class="o">)</span>
File <span class="s2">"pygpu/gpuarray.pyx"</span>, line 1021, <span class="k">in </span>pygpu.gpuarray.GpuContext.<span class="k">**</span>cinit<span class="k">**</span> <span class="o">(</span>pygpu/gpuarray.c:13468<span class="o">)</span>
GpuArrayException: Error loading library: <span class="nt">-1</span>
<span class="o">[</span>Elemwise<span class="o">{</span>exp,no_inplace<span class="o">}(</span>&lt;TensorType<span class="o">(</span>float64, vector<span class="o">)&gt;)]</span>
Looping 1000 <span class="nb">times </span>took 4.158732 seconds
Result is <span class="o">[</span> 1.23178032 1.61879341 1.52278065 ..., 2.20771815 2.29967753
1.62323285]
Used the cpu</code></pre></figure> <h2 id="installation-of-cuda-toolkit-and-driver">Installation of CUDA Toolkit and Driver</h2> <p>This part is well documented by <a href="http://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#axzz4cwRN8XWN">NVIDIA official guide</a> except that some small steps are either too brief (can be not so actionable) or outdated. This post will cover all the commands step by step in an actionable way, for detailed explanations one can always refer to the official guide.</p> <h1 id="pre-installation-actions">Pre-installation Actions</h1> <p>This step is 100% following <a href="http://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#pre-installation-actions">NVIDIA official guide: Pre-installation Actions</a>. First run the following commands to verify system requirements are met:</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="c"># Verify You Have a CUDA-Capable GPU</span>

<span class="nv">$ </span>lspci | <span class="nb">grep</span> <span class="nt">-i</span> nvidia

<span class="c"># Verify You Have a Supported Version of Linux</span>

<span class="nv">$ </span><span class="nb">uname</span> <span class="nt">-m</span> <span class="o">&amp;&amp;</span> <span class="nb">cat</span> /etc/<span class="se">\*</span>release

<span class="c"># Verify the System Has gcc Installed</span>

<span class="nv">$ </span>gcc <span class="nt">--version</span>

<span class="c"># Install Correct Kernel Headers and Development Packages for Red Hat</span>

<span class="nv">$ </span><span class="nb">sudo </span>yum <span class="nb">install </span>kernel-devel-<span class="si">$(</span><span class="nb">uname</span> <span class="nt">-r</span><span class="si">)</span> kernel-headers-<span class="si">$(</span><span class="nb">uname</span> <span class="nt">-r</span><span class="si">)</span></code></pre></figure> <p>Now you go to <a href="http://developer.nvidia.com/cuda-downloads">NVIDIA CUDA Toolkit Downloads</a>. Below is what I chose for my Red Hat EL7 machine. It’s a fresh installation so I didn’t have to deal with conflicting previous installations. But for people having previous CUDA installation, please refer to <a href="http://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#handle-uninstallation">Handle Conflicting Installation Methods</a>.</p> <p class="cuda_cudnn_img"><img src="/assets/cuda_cudnn_post/img/cuda_download.png" alt="Alt text" width="80%"/></p> <h1 id="package-manager-installation">Package Manager Installation</h1> <p>As I mentioned early, I chose to download the <code class="language-plaintext highlighter-rouge">rpm(local)</code> installer option, so I need to use package manager installation. This step we’ll following 80% of the <a href="http://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#package-manager-installation">NVIDIA official guide: Package Manager Installation</a> and add/modify some steps I regard neccessary but not clear in the official guide.</p> <p>For users choosing <code class="language-plaintext highlighter-rouge">runfile</code>, please refer to <a href="http://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#runfile">Runfile Installation</a>.</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="c"># Add third-party repository EPEL to yum repolist</span>

<span class="nv">$ </span>wget http://dl.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-9.noarch.rpm
<span class="nv">$ </span>rpm <span class="nt">-ivh</span> epel-release-7-9.noarch.rpm

<span class="c"># Verify you have EPEL now</span>

<span class="nv">$ </span>yum repolist

<span class="c"># Search in EPEL for dkms and libvdpau</span>

<span class="c"># which are dependencies of CUDA</span>

<span class="nv">$ </span>yum <span class="nt">--enablerepo</span><span class="o">=</span>epel info dkms
<span class="nv">$ </span>yum <span class="nt">--enablerepo</span><span class="o">=</span>epel info libvdpau

<span class="c"># Address custom xorg.conf, if applicable</span>

<span class="c"># I don't have xorg.conf before so it's fine</span>

<span class="c"># if you have, please follow official guide</span>

<span class="c"># Install cuda finally</span>

<span class="c"># takes 10 mins or so</span>

<span class="nv">$ </span><span class="nb">sudo </span>rpm <span class="nt">-i</span> cuda-repo-rhel7-8-0-local-ga2-8.0.61-1.x86_64.rpm
<span class="nv">$ </span><span class="nb">sudo </span>yum clean all
<span class="nv">$ </span><span class="nb">sudo </span>yum <span class="nb">install </span>cuda</code></pre></figure> <h1 id="post-installation-actions">Post-installation Actions</h1> <p>You only need to add <code class="language-plaintext highlighter-rouge">/usr/local/cuda-8.0/bin</code> (version number can vary, please check) to your <code class="language-plaintext highlighter-rouge">PATH</code> environment variable (either by doing as in below or put into your <code class="language-plaintext highlighter-rouge">.bashrc</code> file).</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span><span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span>/usr/local/cuda-8.0/bin:<span class="nv">$PATH</span></code></pre></figure> <p>To verify GPUs can be accessed, three small steps are needed</p> <ul> <li> <p>restart the machine</p> </li> <li> <p>verify the driver version by running <code class="language-plaintext highlighter-rouge">cat /proc/driver/nvidia/version</code></p> </li> <li> <p>run <code class="language-plaintext highlighter-rouge">deviceQuery</code> cuda sample binary, in steps as follow</p> </li> </ul> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="c"># Copy CUDA samples to your personal directory</span>

<span class="c"># so that you have write permission</span>

<span class="nv">$ </span>cuda-install-samples-8.0.sh &lt;your-target-directory&gt;
<span class="nv">$ </span><span class="nb">cd</span> &lt;your-target-directory&gt;/NVIDIA_CUDA-8.0_Samples

<span class="c"># Compile samples</span>

<span class="nv">$ </span>make

<span class="c"># Running deviceQuery</span>

<span class="nv">$ </span><span class="nb">cd </span>bin/x86_64/linux/release
<span class="nv">$ </span>./deviceQuery</code></pre></figure> <p>Here’s what you’ll get after running <code class="language-plaintext highlighter-rouge">deviceQuery</code>:</p> <p class="cuda_cudnn_img"><img src="/assets/cuda_cudnn_post/img/deviceQuery.png" alt="Alt text" width="80%"/></p> <h1 id="status-of-test-examples-able-to-run-on-gpus-but-cudnn-complaints">Status of test examples: able to run on GPUs but cuDNN complaints</h1> <p>Run <code class="language-plaintext highlighter-rouge">example_1</code> on GPU mode:</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="o">(</span>theano_test<span class="o">)</span> <span class="o">[</span>kehang]<span class="nv">$ THEANO_FLAGS</span><span class="o">=</span><span class="nv">device</span><span class="o">=</span>cuda python example_1.py</code></pre></figure> <p>Result of <code class="language-plaintext highlighter-rouge">example_1</code> for GPU mode:</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">Can not use cuDNN on context None: cannot compile with cuDNN. We got this error:
/usr/bin/ld: cannot find <span class="nt">-lcudnn</span>
collect2: error: ld returned 1 <span class="nb">exit </span>status

Mapped name None to device cuda: Quadro K2200 <span class="o">(</span>0000:03:00.0<span class="o">)</span>
<span class="o">[</span>GpuElemwise<span class="o">{</span>exp,no_inplace<span class="o">}(</span>&lt;GpuArrayType&lt;None&gt;<span class="o">(</span>float64, <span class="o">(</span>False,<span class="o">))&gt;)</span>, HostFromGpu<span class="o">(</span>gpuarray<span class="o">)(</span>GpuElemwise<span class="o">{</span>exp,no_inplace<span class="o">}</span>.0<span class="o">)]</span>
Looping 1000 <span class="nb">times </span>took 0.535818 seconds
Result is <span class="o">[</span> 1.23178032 1.61879341 1.52278065 ..., 2.20771815 2.29967753
1.62323285]
Used the gpu</code></pre></figure> <p>Hooray! <code class="language-plaintext highlighter-rouge">example_1</code> is able to access to GPU <code class="language-plaintext highlighter-rouge">Quadro K2200</code> and the wallclock has been reduced by a factor of 8 (from 4.15 sec to 0.53 sec.)</p> <p>But also it shows that <code class="language-plaintext highlighter-rouge">cannot find -lcudnn</code>, which will be our next installation part: <strong>cuDNN installation</strong>.</p> <h2 id="installation-of-cudnn">Installation of cuDNN</h2> <p>This part of installation is relatively easy, and we’ll mainly follow <a href="http://www.pyimagesearch.com/2016/07/04/how-to-install-cuda-toolkit-and-cudnn-for-deep-learning/">AWS EC2 guide 2</a>. But still some steps of it need modified or better explained.</p> <h1 id="cudnn-download">cuDNN Download</h1> <p>To obtain the cuDNN library, one needs</p> <ul> <li>create an account to join <a href="https://developer.nvidia.com/accelerated-computing-developer">NVIDIA developer program</a>.</li> <li>download <a href="https://developer.nvidia.com/rdp/cudnn-download">cuDNN</a></li> </ul> <p class="cuda_cudnn_img"><img src="/assets/cuda_cudnn_post/img/cudnn_download.png" alt="Alt text" width="80%"/></p> <p>I chose <strong><code class="language-plaintext highlighter-rouge">cuDNN Library v5.1 for Linux</code></strong> not <code class="language-plaintext highlighter-rouge">v6.0</code> is because latest <code class="language-plaintext highlighter-rouge">theano</code> can only utilize up to <code class="language-plaintext highlighter-rouge">v5.1</code> (<code class="language-plaintext highlighter-rouge">v6.0</code> has a conflict with <code class="language-plaintext highlighter-rouge">theano</code>, but maybe future <code class="language-plaintext highlighter-rouge">theano</code> can cooperate)</p> <h1 id="cudnn-unpack-and-install">cuDNN Unpack and Install</h1> <p>Copy the download onto your machine, and unpack it</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="nv">$ </span><span class="nb">tar</span> <span class="nt">-zxf</span> cudnn-8.0-linux-x64-v5.1.tgz</code></pre></figure> <p>Copy cuDNN header file and library files to appropriate <code class="language-plaintext highlighter-rouge">include</code> and <code class="language-plaintext highlighter-rouge">lib64</code> directories.</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="c"># change directory to the unpacked folder of cudnn</span>

<span class="nv">$ </span><span class="nb">cd </span>cuda

<span class="c"># copy related files to /usr/local/cuda/lib64 or /usr/local/cuda/include</span>

<span class="c"># -av will keep the symbolic links as is during copying</span>

<span class="nb">sudo cp</span> <span class="nt">-av</span> lib64/_ /usr/local/cuda/lib64/
<span class="nb">sudo cp</span> <span class="nt">-av</span> include/_ /usr/local/cuda/include/

<span class="c"># Update your environment variables in bash session</span>

<span class="c"># or put them in your .bashrc file</span>

<span class="nb">export </span><span class="nv">LD_LIBRARY_PATH</span><span class="o">=</span>/usr/local/cuda/lib64/:<span class="nv">$LD_LIBRARY_PATH</span>
<span class="nb">export </span><span class="nv">LIBRARY_PATH</span><span class="o">=</span>/usr/local/cuda/lib64/:<span class="nv">$LIBRARY_PATH</span></code></pre></figure> <h1 id="status-of-test-examples-run-on-gpus-without-any-complaints">Status of test examples: run on GPUs without any complaints</h1> <p>Run <code class="language-plaintext highlighter-rouge">example_1</code> on GPU mode:</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="o">(</span>theano_test<span class="o">)</span> <span class="o">[</span>kehang]<span class="nv">$ THEANO_FLAGS</span><span class="o">=</span><span class="nv">device</span><span class="o">=</span>cuda python example_1.py</code></pre></figure> <p>Result of <code class="language-plaintext highlighter-rouge">example_1</code> for GPU mode:</p> <figure class="highlight"><pre><code class="language-bash" data-lang="bash">Using cuDNN version 5110 on context None
Mapped name None to device cuda: Quadro K2200 <span class="o">(</span>0000:03:00.0<span class="o">)</span>
<span class="o">[</span>GpuElemwise<span class="o">{</span>exp,no_inplace<span class="o">}(</span>&lt;GpuArrayType&lt;None&gt;<span class="o">(</span>float64, <span class="o">(</span>False,<span class="o">))&gt;)</span>, HostFromGpu<span class="o">(</span>gpuarray<span class="o">)(</span>GpuElemwise<span class="o">{</span>exp,no_inplace<span class="o">}</span>.0<span class="o">)]</span>
Looping 1000 <span class="nb">times </span>took 0.536293 seconds
Result is <span class="o">[</span> 1.23178032 1.61879341 1.52278065 ..., 2.20771815 2.29967753
1.62323285]
Used the gpu</code></pre></figure> <p>Although <code class="language-plaintext highlighter-rouge">example_1</code> doesn’t show time advantage of using <code class="language-plaintext highlighter-rouge">cuDNN</code> but it clearly shows it’s using cuDNN smoothly.</p> <p>For <code class="language-plaintext highlighter-rouge">example_2</code>, which is a convolutional neural network application, it can run on GPUs as well (below right-corner video). After switching to GPUs, the training process is sppeded up by at least 6 times (from 291 sec/epoch to 45 sec/epoch, I guess you can immediately tell from the progress bar)</p> <iframe width="360" height="215" src="https://www.youtube.com/embed/14sQNwBFv9s" frameborder="0" allowfullscreen=""></iframe> <iframe width="360" height="215" src="https://www.youtube.com/embed/rO1qwGVB47w" frameborder="0" allowfullscreen=""></iframe> <p>But anyways, hope you can enjoy the installation guide of CUDA and cuDNN on Red Hat. You can leave your comments on Youtube if you have any questions or suggestions.</p>]]></content><author><name></name></author><category term="tools"/><summary type="html"><![CDATA[I’ve been building neural networks for my chemical space deep learning research since last year. Speedup of training is always one of the central topics. Recently my research group purchased a Quadro K2200 for our Red Hat workstation. I thought it’s a good opportunity of accelerating the computation by switching to GPU. The benefit detail for my research projects will probably be covered in later posts.]]></summary></entry><entry><title type="html">Smart Review Summarization Project</title><link href="https://kehang.github.io/blog/2016/smart-review-summarization-project/" rel="alternate" type="text/html" title="Smart Review Summarization Project"/><published>2016-08-09T03:21:51+00:00</published><updated>2016-08-09T03:21:51+00:00</updated><id>https://kehang.github.io/blog/2016/smart-review-summarization-project</id><content type="html" xml:base="https://kehang.github.io/blog/2016/smart-review-summarization-project/"><![CDATA[<p>Early this year, my friends in <a href="http://hkh12.scripts.mit.edu/mlgp/mlgp.html">Machine Learning Study Group</a> and I were learning natural language processing. To make the learning experience more interesting, we thought we could strike a real life project; three of us are online shoppers and all know the pain of scanning through lots of reviews before placing purchasement. What if there’s a tool that can summarize all the thousands of reviews for us? So we started.</p> <p>Here I present our joint work <strong>SRS</strong>, <a href="http://srs.mit.edu">srs.mit.edu</a>, which is to save us. By using Natural Language Processing and Machine Learning techniques, <strong>SRS</strong> is able to instantly help summarize customers’ opinions on various aspects of a particular product. On top of that, it also enables users to compare sentiment scores for two similar products.</p> <h2 id="workflow">Workflow</h2> <p>An <strong>SRS</strong> user is only required to type in a product ID or product URL from Amazon, the rest of work will be taken care of by <strong>SRS internal workflow</strong>.</p> <p class="srs_img"><img src="/assets/srs_post/img/workflow.png" alt="Alt text" width="80%"/></p> <p>Once <em>SRS front-end</em> gets user query, it invokes <em>Review Scraper</em> to work, which collects reviews and stores into <em>SRS Database</em>. Then <em>Aspect Classifier</em> starts to analyze each sentence in the reviews, classifying which aspects the review is discussing (e.g., <code class="language-plaintext highlighter-rouge">I like this camera, it can last whole day without re-charging</code> will be classified as <code class="language-plaintext highlighter-rouge">battery</code>). Later <em>sentiment analyzer</em> aggregates review positivity for each aspect and send the summary to <em>SRS front-end</em> to present. A typical summary box plot is shown below.</p> <p class="srs_img"><img src="/assets/srs_post/img/typical_plot.png" alt="Alt text" width="80%"/></p> <h2 id="review-scraper">Review Scraper</h2> <p>Once user requests for a product, <strong>Review Scraper</strong> will first be triggered if the product is not recorded before in database. By using <code class="language-plaintext highlighter-rouge">python-amazon-simple-product-api</code>, <strong>Review Scraper</strong> is able to scrape reviews page by page.</p> <p>This process can be fairly long especially for products with thousands of reviews. A time limit of 30 seconds is set and top <em>helpful</em> reviews are first scraped so that users can get most relevant information within reasonable time. In order to make reviews gradually complete, <strong>Review Scraper</strong> is able to continue scraping from where previous scraping stops. Once a product’s reviews are considered complete (a certain ratio between number of reviews in database and total number of reviews online), future requests for that product don’t trigger <strong>Review Scraper</strong> any more.</p> <h2 id="aspect-classifiers">Aspect Classifiers</h2> <p>One review usually contains many points, covering more than one aspects of a product. One of the biggest values this project creates is provide sentiment scores for each aspect of the product so that users are informed in a much deeper level compared with given an overall score. So the most crucial part of <strong>SRS</strong> is to classify each sentence into several aspects.</p> <p>Currently, we’ve designed an extensible classification framework with three interchangeable classifiers: <strong>maxEntropy</strong>, <strong>word2vec</strong>, <strong>word2vec_svm</strong>.</p> <h3 id="maxentropy">maxEntropy</h3> <p>Maximum entropy is a supervised learning algorithm that often used in text classification. The idea of maximum entropy in classification is similar to that of the Naive Bayes that they are both discriminative model that estimate the conditional probability</p> \[P(a|s)\] <p>of each predefined product aspect given a sentence/fragment from review. Unlike Naive Bayes, maximum entropy does not hurt by strong independence assumption in the presence of overlapping features that defines the product aspect. For example, if the word <code class="language-plaintext highlighter-rouge">shoot</code> is a feature for product aspect <code class="language-plaintext highlighter-rouge">picture</code> and <code class="language-plaintext highlighter-rouge">video</code>, then maximum entropy could naturally handle this during training of parameters.</p> <p>The conditional probability in maximum entropy is a parameterized exponential distribution or more precisely it called maximum entropy probability distribution.</p> \[P(a*i|s_j) = \frac{\mathrm{exp}( \lambda_i^\intercal f_i(s_j,a_i))}{\sum*{i} \mathrm{exp}(\lambda_i^\intercal f_i(s_j,a_i))}\] <p>Where \(\lambda_i\) is a vector of parameters for aspect \(i\), \(f_i\) is a vector of feature. The choice of feature is arbitrary and the text classifier will yield the best result when chosing key words relevant to the product aspect. Currently, we defined the feature space based on first 20 key words in the tf-idf ranking and the their values are simply the occurrence of the chosen key words. Choice of feature space can definately exploited to further improve classification in the future. During the training process, the parameter matrix \(\Lambda\) is to be determined by minimizing the negative log of the sum of conditional probability for all sentences assuming all sentences are independent of each other.</p> <p>The maximum entropy has yields a satisfactory accuracy (&gt;60%) that is used as a benchmark for comparing other text classification algorithms.</p> <h2 id="sentiment-analyzer">Sentiment Analyzer</h2> <p>After classification, all the review sentences are grouped by aspects. <strong>Sentiment Analyzer</strong> is designed to go through sentences aspect by aspect and assigns sentiment scores for each single sentence. Eventually each aspect has a distribution of sentiment, which makes it ready for final rendering in <strong>SRS front-end</strong> as well as comparison with another product if necessary.</p>]]></content><author><name></name></author><category term="fun_project"/><summary type="html"><![CDATA[Early this year, my friends in Machine Learning Study Group and I were learning natural language processing. To make the learning experience more interesting, we thought we could strike a real life project; three of us are online shoppers and all know the pain of scanning through lots of reviews before placing purchasement. What if there’s a tool that can summarize all the thousands of reviews for us? So we started.]]></summary></entry></feed>