デジタル・デザイン・ラボラトリーな日々

アラフィフプログラマーが数学と物理と英語を基礎からやり直す。https://qiita.com/yaju

シグモイド関数を理解してみる

はじめに

機械学習の学習すると必ず出てくる用語にシグモイド関数があります。
今回はこれを理解してみたいです。

シグモイド曲線

シグモイド曲線は入力した値を0から1の間に収めてくれる関数の1つです。
多くの自然界に存在する事柄は、このようなS字曲線を取ります。
欠点として1に近づくほど1そのものにならない性質があります。

使う理由

分類した場合に「1」と「-1」という2択ではなく、シグモイド関数は確率で分類すると考えます。
Aの確率が80%で、Bの確率が20%といった感じで分類していきます。
シグモイド曲線の値は 0 から 1 の間になるので確率として使用できます。

すべての関数は微分ができるわけではありません。
尖った点があったり、線が切れていると微分はできないのです。
※尖っていた場合、同じ点に対して二つの傾きが計算できてしまうので微分不可能になる。
どうしてシグモイド関数は滑らかな関数なのか - Qiita
自然科学のための数学2014年度第10講

例えば有名な例として絶対値の関数 y=|x| があります。この関数は x=0 で微分できません。
同様にステップ関数も x=0 で微分できません。
f:id:Yaju3D:20190331145627p:plain

シグモイド関数は下図グラフで示すようにステップ関数を滑らかにした形で線が切れてないので微分が可能になるのです。

数式

f(x) = \displaystyle  \frac{1}{1+e^{-ax}}

グラフ

f:id:Yaju3D:20170602002203p:plain

下図に示すステップ関数(step function)を滑らかにしたものであると見なすことができる。
f:id:Yaju3D:20170603024904p:plain

シグモイド関数の性質

いいサイトがあった。 risalc.info

単調増加性

数学での単調とは「上がったり下がったりがない」という意味です。
放物線は、単調減少と単調増加があります。
f:id:Yaju3D:20181104162349p:plain

シグモイド関数は、S字を描きますが徐々に上がり続けて下がりませんよね。

シグモイド関数の微分

前提

sigmoid(x) = \displaystyle \frac{1}{1 + e^{-x}}

結論

f(x)=sigmoid(x) とした時、
f(x)′=f(x)(1−f(x))

導出

逆数の微分

まず、逆数の微分公式を確認しておきます。
\displaystyle \left(\frac {1}{f}\right)^{\prime}=\frac{−f^{\prime}}{f^{2}} です。
逆数の微分公式の証明:「 商の微分公式の証明と例題
商の微分
\left(\dfrac{1}{f(x)}\right)^{\prime} = \displaystyle\lim_{h\to 0}\dfrac{\frac{1}{f(x+h)}-\frac{1}{f(x)}}{h}
=\displaystyle\lim_{h\to 0}\dfrac{\frac{f(x)}{f(x)f(x+h)}-\frac{f(x+h)}{f(x)f(x+h)}}{h}
=\displaystyle \lim_{h\to 0}\dfrac{f(x)-f(x+h)}{hf(x)f(x+h)}
=\displaystyle \lim_{h\to 0}-\dfrac{1}{f(x)f(x+h)}\cdot\dfrac{f(x+h)-f(x)}{h}
=-\dfrac{f'(x)}{{f(x)}^2}

シグモイド関数は 1+exp(−x) の逆数なので
f^{\prime}(x)=\left(\displaystyle \frac {1}{1+exp(−x)}\right)^{\prime}

=\displaystyle \frac{-(1+exp(-x))^{\prime}}{(1+exp(-x))^{2}}

=\displaystyle \frac {exp(−x)}{\left(1+exp(−x)\right)^{2}}

=\displaystyle \frac {1}{1+exp(−x)} \frac {exp(−x)}{1+exp(−x)}

=\displaystyle \frac {1}{1+exp(−x)} \left(\frac {1+exp(−x)}{1+exp(−x)}−\frac {1}{1+exp(−x)}\right)

=\displaystyle \frac {1}{1+exp(−x)} \left(1−\frac {1}{1+exp(−x)}\right)

ここで、
f(x)=\displaystyle \left(\frac {1}{1+exp(-x)}\right)
であるから、式を当てはめると

=f(x)\left(1−f(x)\right)

もう少し詳しい説明を書いてみました。
yaju3d.hatenablog.jp

グラフ

左図はシグモイド関数の微分したグラフ、右図は正規分布関数のグラフです。形が似ていますよね。
正規分布関数との類似性と計算のしやすさの2つが、機械学習でシグモイド関数がよく使われる理由なのです。 f:id:Yaju3D:20190523004451p:plain

使い道

シグモイド関数の微分(f(x)\left(1−f(x)\right) )はどこに使われるのか、最急降下法(勾配降下法)や 誤差逆伝播法(バックプロパゲーション)にてパラメーターの更新に使われます。
以前はシグモイド関数が主流でしたが、最近ではReLU関数などが使われています。これは勾配消失の問題です。
シグモイド関数の微分の最大値は0.25となるため、ディープラーニングで層を増やしていくと勾配が消失してしまうのです。その点、ReLU関数の場合は最大値が1.00で勾配消失がしにくくなる。

ロジスティック回帰との関係

ロジスティック回帰は、以下の数式で表現できます。

y=\displaystyle \frac {1} {1+exp(-(b_{1}X_{1}+b_{2}X_{2}+b_{3}X_{3}+ \cdots  + b_{i}X{i}+b_{0}))}

シグモイド関数と同じ形であり、数式の中の「b_{1}X_{1}+b_{2}X_{2}+b_{3}X_{3}+ \cdots  + b_{i}X{i}+b_{0}」という部分は重回帰分析と同じです。
よって、ロジスティック回帰のグラフを描くとシグモイド関数と同様になります。

ロジスティック回帰の最尤関数

統計学や機械学習をを勉強していると「尤度(ゆうど)」という概念に出会います。
尤もらしい(もっともらしい)ということで、最尤度は最高にもっともらしいってことですね。
尤度とはある事象が観測された時、それぞれの事象の確率の確率分布関数のパラメータがどれだけ尤もらしいかを測る量で、事象の同時確率として定義されます。
※同時確率とは、サイコロを2回投げた場合、1回目に1の目が出て、2回目に2が出る確率は?って聞かれたら、まず\frac {1}{6}で1が出て、次に\frac {1}{6}で2が出て、それが続けて起こる確率は掛け算を使って、\frac {1}{6}\cdot\frac {1}{6}=\frac {1}{36}

回帰の時の2乗誤差だった場合は最小化した値を求めるのが目的だったのに対し、今考えるのは同時確率で確率が高くなって欲しいので最大化するのが目的になります。

上記のロジスティック回帰をシグマに展開すると下記式になります。
PD_{i}=\displaystyle \frac{1}{1+exp(-(b_{0}+ \displaystyle \sum_{p=1}^{K}b_{p}X_{i,p}))}

同時確率を一般式すると下記式になります。
L=\displaystyle \prod_{i=1}^{N}PD_{i}^{Y_i} \left(1-PD_{i}\right)^{1-Y_{i}}

掛け算というのは計算が大変なので、掛け算を足し算にする「対数」を使って尤度関数を表すと下記式になります。
L=\displaystyle \log \left(\prod_{i=1}^{N}PD_{i}^{Y_i} \left(1-PD_{i}\right)^{1-Y_{i}}\right)

=\displaystyle \sum_{i=1}^{N}\left(Y_{i}\cdot\log\left(PD_{i}\right)+\left(1-Y_{i}\right)\cdot\log\left( 1-PD_{i}\right)\right)

活性化関数として

人間の脳は神経細胞(ニューロン)のネットワークで構成されています。この最小単位の神経細胞(ニューロン)を模倣したモデルがパーセプトロンとなります。このモデルは入力値が閾値を超えた場合に「1」を出力(この場合を「ニューロンが発火する」と表現)し、そうでない場合は「0」を出力します。このように入力値から出力値を生むまでの計算を活性化関数と呼びます。

パーセプトロンでは活性化関数に下図のステップ関数(入力に対してある閾値を境に階段(ステップ)のように出力が1か0か決まる)を使っていました。
f:id:Yaju3D:20170603024904p:plain

ニューラルネットワークは学習をする際に微分を含む計算を行うため、微分するとゼロになるステップ関数は都合が悪いことからステップ関数に似た形で微分してもゼロにならないシグモイド曲線(sigmoid)などが使用されるようになりました。
※微分するとゼロになると都合が悪いのは、値を微分値で更新しても同じ値のままになってしまうからです。

下図のExcelで、106行目の重みの値を更新する際に、前行(105行目)の重みと同行の赤枠「学習係数 x 勾配」の値を加算した値にします。
もし、勾配の値がゼロになってしまうと次の重みの値が更新されないため同じ値のままになり都合が悪くなります。
つまり、そこが正解値ならいいのですが、まだ途中段階で更新が止まってしまうのが良くないわけです。
f:id:Yaju3D:20181031012450p:plain

最後に

今回、シグモイド関数とロジスティック回帰の関係が分かりました。
自分がまだ微分の知識が足りてないってことが分かりました、数式はまだまだ苦手だな。

サポートベクターマシーン(SVM)を理解してみる

はじめに

ディープラーニング(深層学習)の理解もまだ進んでいないわけですが、今回は勝手に古い技術と思い込み何も理解しようとすらしていなかった サポートベクターマシン(Support Vector MachineSVM)に着目してみます。

サポートベクターマシンとは

サポートベクターマシン(Support Vector MachineSVM)は、1995年頃にAT&TV.Vapnik(ウラジミール・ヴァプニーク(Vladimir Vapnik;1936-))が発表したパターン識別用の教師あり機械学習方法であり、局所解収束の問題が無い長所がある。「マージン最大化」というアイデア等で汎化能力も高め、現在知られている方法としては、最も優秀なパターン識別能力を持つとされている。
参照:サポートベクターマシン(SVM)

機械学習には大きく分けて「識別関数」「識別モデル」「生成モデル」の3つの種類があります。このなかで識別関数が確率を使わないので初心者が入門するのに最適です。

識別問題には線形分離可能と線形分離不可能がある。
f:id:Yaju3D:20180901022342p:plain

パーセプトロンという基礎的な識別関数の学習手法は、誤差関数の勾配を利用してどんどん誤差関数を小さくしていくというものであるが、この手法には下記の2点の課題がある。

  • モデルの汎化能力が保証されない。
  • 線形分離可能な問題で利用できない。

SVMは2クラスの分類を行うための機械学習の手法で、大雑把に言うとパーセプトロンという基礎的な識別関数に「マージン最大化」と「カーネル関数」という考え方を導入して上記の課題に対応したものである。

マージン最大化

学習データの中で最も他クラス と近い位置にいるもの(サポー トベクタ)を基準として、その ユークリッド距離が最大になる ように識別面を決める。

汎化能力とは学習時に与えられた訓練データだけに対してだけでなく、未知の新たなデータに対するクラスラベルや関数値も正しく予測できる能力のことを指す。 そもそも、単純パーセプトロンのような機械学習を利用する目的は、スパムメールの例であれば、学習では使っていないメールでも上手くスパムかどうかを分類することである。 決して、以前にきたことのあるメールだけを分類すればいいというわけではない。

qiita.com

カーネルトリック

カーネルトリックとは、元々のデータ空間から高次元空間にデータを写像し、その高次元空間上で線形データ解析を行うことを指す。

ソフトマージンで線形分離不可能な場合でも、分離超平面を決定することができますが、 所詮線形分離なので、性能には限界があります。 カーネルトリックはその限界を取り払い、SVMが注目されるきっかけを作った手法です。

SVMの利点・欠点

参照:SVMってなに?

利点

  • データの特徴の次元が大きくなっても識別精度が良い
  • 最適化すべきパラメータが少ない
  • パラメータの算出が容易

欠点

  • 学習データが増えると計算量が膨大になる (「次元の呪い」の影響が顕著)
  • 基本的には2クラスの分類にしか使えない

線形SVN

線形SVNは「ハードマージン」と「ソフトマージン」に分けられます。

ハードマージン

SVMパターン認識手法の一種です。 ハードマージンSVMはその中でも一番基本となるものです。

shogo82148.github.io

ソフトマージン

線形分離不可能(直線では分けられない場合)にはうまく行きません。 それを解決するのがソフトマージンSVMです。
ソフトマージンSVMは、データにノイズが混じっている場合にもある程度強いという特徴があります。

shogo82148.github.io

参照

d.hatena.ne.jp d.hatena.ne.jp d.hatena.ne.jp

Google Colaboratory上でmatplotlibのアニメーションを再生する

はじめに

最近は、Anacodaを使わずにGoogle Colaboratoryを使用しています。
Google ChromでGoogle Colaboratory にアクセスすれば、すぐにPythonが使えますからね。

下記サイトでは勾配降下法 (Gradient Descent)のグラフをアニメーション化しており、かっこいいです。
sinhrks.hatenablog.com

Google Colaboratory でPython 3に変更して動かして見たのですが、アニメーションは動きませんでした。

アニメーション

下記サイトを参考に Jupyter notebookと同じようにmatplotlibのnbagg を有効にしてみたりしたのですが、駄目でした。 qiita.com

ネットの検索条件「Google Colaboratory animation」と英語化して、見つけたのが下記サイトとなります。 medium.com

技術的なことは、下記サイトが参考になります。
「JupiterにMatplotlibアニメーションをインタラクティブJavaScriptウィジェットとして埋め込む」
louistiao.me

下記プログラムをGoogle Colaboratoryに貼り付けると、アニメーションが表示されます。

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation, rc
from IPython.display import HTML
# animate over some set of x, y
x = np.linspace(-4, 4, 100)
y = np.sin(x)
# First set up the figure, the axes, and the plot element
fig, ax = plt.subplots()
plt.close()
ax.set_xlim(( -4, 4))
ax.set_ylim((-2, 2))
line1, = ax.plot([], [], lw=2)
line2, = ax.plot([], [], lw=2)
# initialization function: plot the background of each frame
def init():
    line1.set_data(x, y)      
    return (line1,)
# animation function: this is called sequentially
def animate(i):
  at_x = x[i]
  
  # gradient_line will have the form m*x + b
  m = np.cos(at_x)
  b = np.sin(at_x) - np.cos(at_x)*at_x
  gradient_line = m*x + b
  
  line2.set_data(x, gradient_line)
  return (line2,)
anim = animation.FuncAnimation(fig, animate, init_func=init, frames=100, interval=100, blit=True)
rc('animation', html='jshtml')
anim

f:id:Yaju3D:20180731234424g:plain

アニメーションの保存

下記サイトを見つけました。 qiita.com

Google Colaboratory上ではどうすれば出来るのかを検討したところ、mp4形式なら出来ることが分かりました。 参照:Sum approximation

準備として、ffmpeg をインストールする必要があります。

# To make an animation, we need ffmpeg
!apt-get update && apt-get install ffmpeg

先程のプログラムの下側にあるコードを変更します。

anim = animation.FuncAnimation(fig, animate, init_func=init, frames=100, interval=100, blit=True)

# add code
plt.rcParams['animation.ffmpeg_path'] = '/usr/bin/ffmpeg' # For google colab
HTML(ani.to_html5_video())

#rc('animation', html='jshtml')
#anim

表示された動画にある「︙」をクリックすれば、ダウンロード出来ます。
※下図はキャプチャした静止画です。
f:id:Yaju3D:20180812130117p:plain

Twitterは動画形式のmp4対応しているので、ツイートする分には問題ありません。
どうしてもgifにしたい場合には、オンライン上でmp4からgifに変換できます。
www.aconvert.com

gif保存には調査が必要

上記方法より先にgif形式を試したのですが、PillowWriterでインポートエラーになりました。
もしかしたら出来る方法があるかも知れませんが、追求はやめました。

from matplotlib.animation import PillowWriter

anim = animation.FuncAnimation(fig, animate, init_func=init, frames=100, interval=100, blit=True)
anim.save("gradientline.gif", writer=PillowWriter(fps=60))

#ImportError: cannot import name 'PillowWriter'

最後に

後で、勾配降下法 (Gradient Descent)のグラフをアニメーション化して公開する予定です。
今回は紹介まで。

【2018/05/01】アニメーション化しました。 qiita.com

Google ColaboratoryでGitHubのCSVデータをpandasに読み込む

はじめに

最近は、Anacodaを使わずにGoogle Colaboratoryを使用しています。
Google ChromでGoogle Colaboratory にアクセスすれば、すぐにPythonが使えますからね。

機械学習を学ぶ上では、サクッと使えるデータが必要です。

Google Colaboratoryでファイルの入出力

Google Colaboratoryでファイルの入出力については、ローカルファイルへアップロードしたり、google driveを使用する方法があります。
それらについては下記サイトを参考にするといいでしょう。 web.archive.org qiita.com

Web上からCSVデータの読み込み

ローカルファイルへアップロードしたり、google driveを使用することすら面倒くさいと思っていて、既にWeb上に置いてあるデータをそのまま使えれば楽ちんだよね。
下記サイトを参考にしたら、統計表一覧 政府統計の総合窓口 GL08020103 のcsvデータが読めました。 blog.neko-ni-naritai.com

import pandas as pd
import urllib.request
from io import StringIO

url = "http://www.e-stat.go.jp/SG1/estat/Csvdl.do?sinfid=000012460662"

#csvを読み込む関数
def read_csv(url):
    print(url)
    res = urllib.request.urlopen(url)
    res = res.read().decode('shift_jisx0213')
    df = pd.read_csv(StringIO( res) )
    return df

#実行
read_csv(url)

f:id:Yaju3D:20180630115212p:plain

では、誰かがあげてあるGitHubのデータも使えるんじゃないかと、機械学習の初心者が学ぶ際に使用する有名なデータのアヤメ(iris)を検索して一番最初に出てきたのを使用しました。
https://github.com/pandas-dev/pandas/blob/master/pandas/tests/data/iris.csv

先程のプログラムのURLを書き換えただけだと、「ParserError: Error tokenizing data. 」となりました。

url = "https://github.com/pandas-dev/pandas/blob/master/pandas/tests/data/iris.csv"

下記記事は、R言語ですがPyhtonでも似たようなものだろうと、Raw つまり データだけが表示されるページにアクセスすればいいようです。 web.archive.org

先程のgithubのサイトに「Raw」ボタンがあったので、そこをクリックした際のURLに書き換えます。また、デコードをSJISからUTF-8に変更します。

import pandas as pd
import urllib.request
from io import StringIO

url = "https://raw.githubusercontent.com/pandas-dev/pandas/master/pandas/tests/data/iris.csv"

#csvを読み込む関数
def read_csv(url):
    print(url)
    res = urllib.request.urlopen(url)
    res = res.read().decode("utf-8")
    df = pd.read_csv(StringIO( res) )
    return df

#実行
read_csv(url)

f:id:Yaju3D:20180630121137p:plain

最後に

自分は面倒くさがり屋です、この「面倒くさがり屋」とはプログラマの三大美徳である「怠惰」「短気」「傲慢」の「怠惰」に匹敵する言葉だったりします。
機械学習を学ぶ上で少しでも敷居を下げれればと思います。

ディープラーニング(深層学習)を理解してみる(識別問題 その2)

はじめに

3月の問題が解決しないまま2ヶ月が経ってしまい、4月分はうっかり記事を飛ばしてしまった。
このままだと5月分の記事も飛ばしてしまうので、あとで埋めるために5月分の記事を立てた。

しばしお待ち下さい。

ディープラーニング(深層学習)を理解してみる(識別問題)

はじめに

前々回の「対数logを理解してみる」と前回の「自然対数の底(ネイピア数) e を理解してみる」では、人工知能に使用する基礎的な数学知識が足りなかったのでシリーズとは脱線して書いてみました。 また、このシリーズで書いていきます。

ニューラルネットワークは、回帰問題と分類問題があります。
これまでやってきたリンゴとミカンの値段を予測するというのが回帰問題で、今回やるのは分類問題(識別問題)となります。

以前フレームワークのTensorFlowを使用してやりましたが、今回はフレームワークを使用しないでやってみます。
yaju3d.hatenablog.jp

識別問題とは

入力データから判断して分類させる問題。今回の例では買えるのか買えないのかを判断させる。 識別分類器としてロジスティック回帰を使う。

ロジスティック回帰とは

ロジスティック回帰は、発生確率を予測する手法です。 基本的な考え方は線形回帰分析と同じなのですが、予測結果が 0 から 1 の間を取るように数式やその前提に改良が加えられています。 今回のように購入有無(買える or 買えない)の2値しかとりえない値を従属変数の実績値として用い、説明変数を用いてその発生確率を説明するという構造になっています。

例題

たかしくんは八百屋へ財布を預かってお使いに行きました。
しかし、たかしくんはお金を 数えられません。

気まぐれおやじ曰く、
リンゴ2個+ミカン3個、リンゴ0個+ミカン16個 なら買えるが、リンゴ3個+ミカン1個、 リンゴ2個+ミカン8個は買えないとのこと。

リンゴ1個+ミカン11個は買えますか? → 識別問題 f:id:Yaju3D:20160421011949p:plain

式で表そうとしてみる…
f:id:Yaju3D:20160421012015p:plain f:id:Yaju3D:20160421012037p:plain

シグモイド曲線

f:id:Yaju3D:20160421012100p:plain
※シグモイド曲線とは、入力した値を0から1の間に収めてくれる関数の1つ
 多くの自然界に存在する事柄は、このようなS字曲線を取る。
 生物の神経細胞、細胞の生存率曲線などなど

予測式(モデル)が作れた!
f:id:Yaju3D:20160421012204p:plain

ロジスティック回帰
f:id:Yaju3D:20160421012219p:plain

プログラム

import numpy as np
import pandas as pd

def p_y_given_x(x, w, b):
    # x, w, b から y の予測値 (yhat) を計算
    def sigmoid(a):
        return 1.0 / (1.0 + np.exp(-a))
    return sigmoid(np.dot(x, w) + b)

def grad(x, y, w, b):
    # 現予測値から勾配を計算
    error = y - p_y_given_x(x, w, b)
    w_grad = -np.mean(x.T * error, axis=1)
    b_grad = -np.mean(error)
    return w_grad, b_grad
  
def gd(x, y, w, b, eta=0.1, num=1000):
    for i in range(1, num):
        # 入力をまとめて処理
        w_grad, b_grad = grad(x, y, w, b)
        w -= eta * w_grad
        b -= eta * b_grad
        e = np.mean(np.abs(y - p_y_given_x(x, w, b)))
        yield i, w, b, e

x = np.array([[2., 3.], [0., 16.], [3., 1.], [2., 8.]])
y = np.array([1., 1., 0., 0.])

# w, b の初期値を作成
w = [-10,-10]
b = 200
gen = gd(x, y, w, b)

for i, w, b, e in gen:
  if (i+1) % 100 == 0:
    print(i+1,b,w,e);

実行結果

100 196.2993226598431 [-22.375      -12.28583744] 0.6420233463035018
200 192.7195561228789 [-34.875      -12.06210203] 0.6420233463023709
300 189.1722948623358 [-47.25687076 -11.79079916] 0.48881302062549137
400 186.7724229463934 [-55.05210822 -11.5078109 ] 0.2665264782982885
500 185.57859640809747 [-58.91482296 -11.29546188] 0.02159297746827703
600 185.4754835638496 [-59.25851476 -11.22680836] 0.007859710540613895
700 185.42589243161035 [-59.4240343  -11.19266882] 0.004766951474845803
800 185.39307584996868 [-59.53360836 -11.16986379] 0.003414942999345553
900 185.36853902132037 [-59.61555193 -11.15273521] 0.002658633400582877
1000 185.34894411228476 [-59.68099871 -11.13901984] 0.002175879579138274

TensorFlowで実行した結果とほぼ同じになっています。
f:id:Yaju3D:20160421012400p:plain

予測結果

0.95848435 ≒ 0.96 f:id:Yaju3D:20160424232047p:plain

自然対数の底(ネイピア数) e を理解してみる

はじめに

前回、対数を理解してみました。 yaju3d.hatenablog.jp

今回は機械学習を学ぶ上で出てくる自然対数の底(ネイピア数 e)とは何かを理解していきます。
間違える人は結構多いですが、e は「自然対数」ではありません。「ネイピア数」あるいは「自然対数の底」と呼ばれる定数です。

自然対数の底とは

対数の底には、10進法を使う常用対数とネイピア数と呼ばれる数学定数を使う自然対数があります。
記号として通常は e が用いられ、その値は、e = 2.71828 18284 59045 23536 02874 71352\cdots と続く無理数超越数となっています。 とても不思議な値ですよね。
不思議な値といえば円周率 \pi = 3.14159265359\cdots も同様に無理数超越数となっています。

qiita.com

最後に

今回はQiitaの方に書いてしまったので、リンクするだけにします。

スポンサーリンク