<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Perceptron on KbWen Blog</title>
    <link>https://www.kbwen.com/tags/perceptron/</link>
    <description>KbWen is a practical technology blog about AI systems, machine learning, Python, data engineering, and software development.</description>
    <generator>Hugo</generator>
    <language>zh-tw</language>
    <image>
      <url>https://www.kbwen.com/images/og-default.png</url>
      <title>KbWen Blog</title>
      <link>https://www.kbwen.com/</link>
    </image>
    
    <lastBuildDate>Tue, 18 Apr 2017 20:13:39 +0800</lastBuildDate><atom:link href="https://www.kbwen.com/tags/perceptron/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>PLA 感知器演算法：權重修正那一步在做什麼</title>
      <link>https://www.kbwen.com/python-machine-learning-basics-ls-pla/</link>
      <pubDate>Tue, 18 Apr 2017 20:13:39 +0800</pubDate><dc:creator>KbWen</dc:creator>
      <guid>https://www.kbwen.com/python-machine-learning-basics-ls-pla/</guid>
      <description>根據林軒田機器學習基石課程實作 PLA，並拆開 w ← w &#43; y·x 這一步：它對內積做了什麼、線性可分為什麼是收斂的前提、資料不可分時為什麼不會停，以及 Pocket PLA 和今天的損失函數換掉了什麼。</description>
      <content:encoded><![CDATA[<h2 id="perceptron-learning-algorithm-pla">Perceptron Learning Algorithm (PLA)</h2>
<p>根據林軒田教授的機器學習基石課程，實作一下這個基礎的機器學習演算法。
我們探討的是監督式學習 (Supervised learning) 大架構下的二元分類 (YES/NO) 問題。</p>
<h3 id="perceptron--linear-binary-classifiers">Perceptron ⇔ linear (Binary) Classifiers</h3>
<p>我們有一組訓練資料 <strong>D</strong>，包含數據 <strong>Xn</strong> 和對應的 <strong>Yn</strong> (在這裡就是 1, -1)；Hypothesis set <strong>H</strong> 代表全部可能的解 (無限多條線)，經過演算法 <strong>A</strong>，從 <strong>H</strong> 找到一個可能的 <strong>g</strong> 與我們的目標函數 <strong>f</strong> 相近。</p>
<p>這個演算法的主要兩大步驟：找到錯誤的點，進行向量修正。
詳細課程可以參考教授的講解！！其中 <code>naive cycle</code> 是常用的作法。</p>
<p>這方法只適用於 <strong>linear separable PLA</strong>。</p>
<p>除此以外，當資料中有雜訊也無法使用這個方式，目前在線性問題上較好的解是用 <strong>Pocket PLA</strong>。</p>
<h2 id="向量修正那一步">向量修正那一步</h2>
<p>「進行向量修正」寫成一行是 <code>w ← w + y·x</code>，把分錯那個點的座標乘上它的標籤（+1 或 -1），加到權重向量上面。分類的依據是內積 <code>w·x</code> 的正負號，所以這一步真正動到的是內積。</p>
<p>把修正後的權重拿去算同一個點，得到的是 <code>(w + y·x)·x</code>，展開就是 <code>w·x + y·(x·x)</code>。<code>x·x</code> 是這個點到原點距離的平方，不會是負的，再乘上標籤，正例的內積往上加、負例往下減，兩邊都朝自己該去的方向移動一段。分界線是垂直於 <code>w</code> 的那條線，<code>w</code> 轉了多少，線就跟著轉多少。</p>
<p>移動一次不保證那個點就分對了。原本的 <code>w·x</code> 如果離零很遠，加一次 <code>x·x</code> 未必翻得過去，得等下一輪再挑到它；更常見的是這個點修好了，線轉過去的同時把原本站對邊的其他點掃到錯的那一側，下一輪換那些點來要求修正。</p>
<p>這一步有兩件事沒有做。加上去的量是 <code>x·x</code>，只跟這個點離原點多遠有關，跟它錯得多嚴重無關，差一點點分錯的點和遠遠站在另一邊的點，推的力道一樣大。分對的點則完全不出力，<code>w</code> 一動也不動。scikit-learn 的文件描述 <code>Perceptron</code> 的預設行為時寫：「It updates its model only on mistakes.」</p>
<h2 id="linear-separable-pla">Linear separable PLA</h2>
<p>首先整理一下資料。把原始格式如 <code>['x0\ty0\tz0\nx1\ty1\tz1\nx2\ty2\tz2\n....']</code> 轉換為 <code>array([[(x0, y0), z0], [(x1, y1), z1], [(x2, y2), z2].....])</code> 的格式。</p>
<p><img
  src="/images/2017/04/ep_pla4.png"
  alt="EP_PLA4"
  loading="lazy"
  fetchpriority="auto"
  decoding="async" width="752" height="624"
>
</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">matplotlib.pyplot</span> <span class="k">as</span> <span class="nn">plt</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span>
</span></span><span class="line"><span class="cl"><span class="c1"># import data</span>
</span></span><span class="line"><span class="cl"><span class="n">train_data</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="s1">&#39;PLA_train.txt&#39;</span><span class="p">,</span><span class="s1">&#39;r&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">pla_data</span> <span class="o">=</span> <span class="n">train_data</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">pla_data</span> <span class="o">=</span> <span class="n">pla_data</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;</span><span class="se">\n</span><span class="s1">&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">nums1</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl"><span class="c1"># figure</span>
</span></span><span class="line"><span class="cl"><span class="n">fig</span> <span class="o">=</span> <span class="n">plt</span><span class="o">.</span><span class="n">figure</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">ax</span> <span class="o">=</span> <span class="n">fig</span><span class="o">.</span><span class="n">add_subplot</span><span class="p">(</span><span class="mi">111</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">ax</span><span class="o">.</span><span class="n">set_xlim</span><span class="p">(</span><span class="o">-</span><span class="mf">0.5</span><span class="p">,</span><span class="mf">0.5</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">ax</span><span class="o">.</span><span class="n">set_ylim</span><span class="p">(</span><span class="o">-</span><span class="mf">0.5</span><span class="p">,</span><span class="mf">0.5</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># data type</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">pla_data</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">pla_data</span><span class="p">[</span><span class="n">nums1</span><span class="p">]</span> <span class="o">=</span> <span class="n">i</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;</span><span class="se">\t</span><span class="s1">&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">pla_data</span><span class="p">[</span><span class="n">nums1</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span><span class="nb">float</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">pla_data</span><span class="p">[</span><span class="n">nums1</span><span class="p">]]</span>
</span></span><span class="line"><span class="cl">    <span class="n">pla_data</span><span class="p">[</span><span class="n">nums1</span><span class="p">]</span> <span class="o">=</span> <span class="p">[(</span><span class="n">pla_data</span><span class="p">[</span><span class="n">nums1</span><span class="p">][</span><span class="mi">0</span><span class="p">],</span> <span class="n">pla_data</span><span class="p">[</span><span class="n">nums1</span><span class="p">][</span><span class="mi">1</span><span class="p">]),</span>
</span></span><span class="line"><span class="cl">     <span class="n">pla_data</span><span class="p">[</span><span class="n">nums1</span><span class="p">][</span><span class="mi">2</span><span class="p">]]</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">pla_data</span><span class="p">[</span><span class="n">nums1</span><span class="p">][</span><span class="mi">1</span><span class="p">]</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">ax</span><span class="o">.</span><span class="n">plot</span><span class="p">(</span><span class="n">pla_data</span><span class="p">[</span><span class="n">nums1</span><span class="p">][</span><span class="mi">0</span><span class="p">][</span><span class="mi">0</span><span class="p">],</span><span class="n">pla_data</span><span class="p">[</span><span class="n">nums1</span><span class="p">][</span><span class="mi">0</span><span class="p">][</span><span class="mi">1</span><span class="p">],</span><span class="s1">&#39;bo&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">elif</span> <span class="n">pla_data</span><span class="p">[</span><span class="n">nums1</span><span class="p">][</span><span class="mi">1</span><span class="p">]</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">ax</span><span class="o">.</span><span class="n">plot</span><span class="p">(</span><span class="n">pla_data</span><span class="p">[</span><span class="n">nums1</span><span class="p">][</span><span class="mi">0</span><span class="p">][</span><span class="mi">0</span><span class="p">],</span><span class="n">pla_data</span><span class="p">[</span><span class="n">nums1</span><span class="p">][</span><span class="mi">0</span><span class="p">][</span><span class="mi">1</span><span class="p">],</span><span class="s1">&#39;rx&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">nums1</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl"><span class="n">pla_data</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">asarray</span><span class="p">(</span><span class="n">pla_data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">pla_data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">train_data</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
</span></span></code></pre></div><p>NAIVE PLA 實作，畫線則是用 <code>ax + by = 0</code>。</p>
<p><img
  src="/images/2017/04/ep_pla5.png"
  alt="EP_PLA5"
  loading="lazy"
  fetchpriority="auto"
  decoding="async" width="749" height="621"
>
</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># naive LS PLA</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">LS_pla</span><span class="p">(</span><span class="n">datas</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">w</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">zeros</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">error</span> <span class="o">=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">    <span class="k">while</span> <span class="n">error</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">error</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="n">x</span><span class="p">,</span><span class="n">s</span> <span class="ow">in</span> <span class="n">datas</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">x</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">array</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">y</span> <span class="o">=</span> <span class="n">w</span><span class="o">.</span><span class="n">T</span><span class="o">.</span><span class="n">dot</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="n">ax</span><span class="o">.</span><span class="n">lines</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="n">lines</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">            <span class="k">except</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="k">pass</span>
</span></span><span class="line"><span class="cl">            <span class="n">xline</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">linspace</span><span class="p">(</span><span class="o">-</span><span class="mf">0.5</span><span class="p">,</span><span class="mf">0.5</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">lines</span> <span class="o">=</span> <span class="n">ax</span><span class="o">.</span><span class="n">plot</span><span class="p">(</span><span class="n">xline</span><span class="p">,</span> <span class="o">-</span><span class="n">xline</span><span class="o">*</span><span class="n">w</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">/</span><span class="n">w</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">            <span class="n">plt</span><span class="o">.</span><span class="n">pause</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="n">np</span><span class="o">.</span><span class="n">sign</span><span class="p">(</span><span class="n">y</span><span class="p">)</span> <span class="o">!=</span> <span class="n">np</span><span class="o">.</span><span class="n">sign</span><span class="p">(</span><span class="n">s</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">                <span class="n">w</span> <span class="o">+=</span> <span class="n">s</span> <span class="o">*</span> <span class="n">x</span>
</span></span><span class="line"><span class="cl">                <span class="n">error</span> <span class="o">=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">error</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">w</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">W</span> <span class="o">=</span> <span class="n">LS_pla</span><span class="p">(</span><span class="n">pla_data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">W</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">plt</span><span class="o">.</span><span class="n">ion</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">plt</span><span class="o">.</span><span class="n">show</span><span class="p">()</span>
</span></span></code></pre></div><h2 id="最終結果">最終結果</h2>
<p><img
  src="/images/2017/04/ep_pla6.png"
  alt="EP_PLA6"
  loading="lazy"
  fetchpriority="auto"
  decoding="async" width="640" height="544"
>
</p>
<h2 id="收斂的前提">收斂的前提</h2>
<p>「這方法只適用於 linear separable」這句話後面有一條證明。維基百科寫的是：「If the training set is linearly separable, then the perceptron is guaranteed to converge after making finitely many mistakes.」Cornell CS4780 的講義寫，資料線性可分時 PLA 會在有限次更新內找到一條把兩類分開的線，資料不可分的話它會一直跑下去。</p>
<p>有限是多少次，講義也給了。先把資料縮放到單位球裡面，更新次數的上界是 <code>1/γ²</code>；γ 一般叫做 margin，量的是理想的那條分界線離最近的資料點有多遠。這個式子裡面沒有資料筆數。一百個點和一百萬個點，只要最窄的那條走廊一樣寬，上界就是同一個數；反過來，兩類靠得越近，γ 越小，<code>1/γ²</code> 抬得越高，一樣是可分的資料，可以慢到跑不完。</p>
<p>保證裡只有「會停」這件事。能把兩類分開的線通常有無限多條，PLA 最後停在哪一條取決於挑點的順序，線與線之間好壞的差別它沒有處理，這一塊由後來的 linear support-vector machine 補上。維基百科寫的是：「it may still pick any solution and problems may admit many solutions of varying quality」。</p>
<p>換一份沒辦法用一條線切開的資料，每一輪照樣挑得到分錯的點，照樣把 <code>y·x</code> 加上去，只是這個來回不會結束。維基百科寫的是：「In case the training set D is not linearly separable, i.e. if the positive examples cannot be separated from the negative examples by a hyperplane, then the algorithm would not converge since there is no solution.」沒有一個 <code>w</code> 能讓所有點同時滿足，演算法就永遠找得到下一個要修的點。</p>
<p>不會停之外還有一件事，權重不會一路變好。每一次修正都只對當下挑到的那個點負責，把它扳回正確的一側，順手把別的點推到錯的一側，所以第 1000 輪手上那條線跟第 999 輪比可能更差，多跑幾輪不等於拿到比較好的答案，隨手在某個輪數收工，拿到的就是那一輪的那條線。</p>
<h2 id="pocket-pla">Pocket PLA</h2>
<p>Pocket PLA 是一個貪婪演算法，把最好的權重握在手上繼續往下算，每次都會比較看有沒有比手上的好。停止方式則是讓它運行一定次數，或是多久沒有變更好等等。這裡暫不詳述。</p>
<h2 id="pocket-的代價">Pocket 的代價</h2>
<p>握著最好的那一組是要付成本的。naive PLA 一輪只看一個點，Pocket 每更新一次都得拿整份資料重算一遍錯誤數，才知道新的那組有沒有比手上的好，一輪的開銷從一個點變成整份資料。維基百科對它找到的解寫的是「appear purely stochastically」，不會隨著訓練逐步逼近，也不保證在指定的步數內出現，所以停下來的時機只能訂在跑滿幾輪、或是多久沒有進步。</p>
<h2 id="後來的更新規則">後來的更新規則</h2>
<p>分錯才動、動的幅度跟錯多少無關，這兩件事讓 PLA 每一輪的計算很省，也讓它在有雜訊的資料上交不出穩定的答案。今天在用的線性分類器多半把這兩件事一起換掉了。</p>
<p>scikit-learn 目前的 <code>Perceptron</code> 是 <code>SGDClassifier</code> 的包裝，文件上寫：「Perceptron() is equivalent to SGDClassifier(loss=&ldquo;perceptron&rdquo;, eta0=1, learning_rate=&ldquo;constant&rdquo;, penalty=None)」。PLA 那條規則變成了 <code>loss</code> 的一個值。</p>
<p>scikit-learn 的 SGD 文件列了幾個常見的損失函數。perceptron 是 <code>max(0, -y·f(x))</code>，只要分對，不管離邊界多近，損失都是 0，這個點就不出力；hinge 是 <code>max(0, 1 - y·f(x))</code>，要分對而且離邊界一個單位以上才歸零，貼著邊界的點還在出力，margin 從這裡進到目標函數裡；log loss 是 <code>log(1 + exp(-y·f(x)))</code>，這個函數在任何地方都不等於零，連分得很開的點都還留著一點梯度，錯得越多梯度越接近滿格。</p>
<p>更新規則換掉之後，停下來的判斷也跟著換。找不到分錯的點這個條件，在不可分的資料上永遠不會成立，取而代之的是損失不再明顯下降、或是跑滿設定的輪數。scikit-learn 的 <code>Perceptron</code> 目前預設 <code>max_iter=1000</code>、<code>tol=0.001</code>，用的是後面這一種。</p>
<p><a href="https://github.com/KbWen/Python_ML/blob/master/LS-PLA.py">My GitHub</a></p>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
