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

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

ディープラーニング(深層学習)を理解してみる(識別問題 その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

プログラム

あとで書く

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

はじめに

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

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

自然対数の底とは

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

qiita.com

最後に

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

対数logを理解してみる

はじめに

機械学習を学んでいると対数logがでてくる。基礎的なことから対数を理解してみたい。
指数はイメージし易いが、対数は分かりにくいと思われている。指数と対数はペアの関係にあり、かけ算とわり算のように逆関係にある。 先ずは、指数の大きさを視覚的にイメージするために、アメリカ・ワシントンにある航空宇宙博物館で公開されていた9分半の映画「パワーズ・オブ・テン」を紹介する。

9分半の映画

パワーズ・オブ・テンです、10の冪(10^{n})の違いを視覚でご覧ください。


Powers of Ten with Japanese translation

対数とは

例えば、2を3回かけ算すると 2 \times 2 \times 2 = 8 になります。これを「2を3乗したら8になる」と言い、以下のように書きます。

  2 ^ 3 = 8

このとき、2 の右上に乗っている 3 のことを「指数」と言います。指数は「1つの数を何回掛けるか」を表しています。
一方、「◯を何乗すれば△になるか」を表す数のことを「対数」と言います。 例えば「2を何乗すれば8になるか」を表す数は以下のように表記され、これを「2を底とする8の対数」と言います。

  log_{2}8 = 3

2 を底をする 8 の対数は 3 ということになります。
もう少し身近な値としてみます。1000 は、10 の 3乗 で「10を底とする1000の対数」は以下のようになります。

  log_{10}1000 = 3

計算の簡略化

対数には大きな桁数でも簡単に計算できるようになるというメリットがある。
それには指数法則と対数法則を知っておく必要がある。

指数法則①

a^{p} \times a^{q} = a^{(p+q)}

3^{4} \times 3^{5} について考える。3^{4} \times 3^{5} を指数を使わない形で表現すると(3 \times 3 \times 3  \times 3)(3 \times 3 \times 3  \times 3 \times 3) となる。 3 を繰り返しかけ算する回数は、  4 + 5 = 9 回である。
よって、3^{4} \times 3^{5} = 3^{(4+5)} となる。

指数法則②

(a^{p})^{q} = a^{(p \times q)}

(5^{3})^{4} について考える。(5^{3})^{4} は、5^{3} を4回繰り返しかけ合わせるので、(5^{3})^{4} = 5^{3} \times 5^{3} \times 5^{3} \times 5^{3} である。指数の部分の合計は、3乗が4回、つまり (3 \times 4) によって計算できる。
よって、 (5^{3})^{4} = 5^{3 \times 4} となる。

指数法則③

(a \times b)^{p} = a^{p} \times b^{p}

(3 \times 7)^{5} について考える。(3 \times 7)^{5} は、(3 \times 7) を5回繰り返しかけ合わせるので、 (3 \times 7)^{5} = (3 \times 7) \times (3 \times 7) \times (3 \times 7) \times (3 \times 7) \times (3 \times 7)
 = (3 \times 3 \times 3 \times 3  \times 3) \times (7 \times 7 \times 7 \times 7  \times 7) 、つまり = 3^{5} \times 7^{5} によって計算できる。
よって、 (3 \times 7)^{5} = 3^{5} \times 7^{5} となる。

対数法則①

かけ算を足し算に変換

log_{a}(M \times N) = log_{a}M + log_{a}N

log_{2}(8 \times 4) = log_{2}32 = 5 = 3 + 2 = log_{2}8 + log_{2}4
注目すべきは、M \times N というかけ算が、log_{a}M + log_{a}N という足し算に変換されているということである。このことが対数を利用してかけ算を足し算へ簡略化して計算する際に生かされる。

対数法則②

わり算を引き算に変換

log_{a}(M \div N) = log_{a}M - log_{a}N

log_{2}(8 \div 4) = log_{2}2 = 1 = 3 - 2 = log_{2}8 - log_{2}4
今回は、M \div N というわり算が、log_{a}M - log_{a}N という引き算の形に変換される。

対数法則③

累乗を簡単なかけ算に変換

log_{a}M^{k} = k \times log_{a}M

log_{2}8^{3} = log_{2}512 = 9 = 3 \times 3 = 3log_{2}8
例えば、2^{29} のような大きな計算も、この対数法則③を使えば一瞬にして近似値を求められる。

底の変換公式

底も自由に変えられるようになります。
\displaystyle \log_{a}b = \frac{(\log_{c}b)}{(\log_{c}a)} = 対数

\displaystyle \log_{2}8 = \frac{(\log_{10}8)}{(\log_{10}2)}
\displaystyle = \frac{0.903089987}{0.301029995} = 3

C# 数学10 「常用対数、ネイピア数e-基本1、自然対数-基本1」e log ln exp

対数の起源

対数を考え出したのはジョン・ネイピアです。
対数(logarithm)の名前の由来は、logos (比、神の言葉)とギリシャ語のarithmos (数) を合わせて logarithms(ロガリズム) という造語でネイピアが考案しました。

ジョン・ネイピア

1550年、宗教戦争が続くスコットランドにネイピアは生まれました。時代はヨーロッパ列強諸国が覇権を争う大航海時代のまっただ中。ネイピアはマーキストン城の城主として官の仕事の他、領民のために農業土木、軍事技術など多くの発明を行うエンジニアとして活躍しました。(中略) 遠洋航海に必要な天文学(船の測位)を支える数学が球面三角法です。数学者でも天文学者でもないネイピアは現実の切実な問題解決を目標としていました。それが天文学的計算の克服です。三角関数の計算の中に現れる大きい数の計算は天文学者を苦しめました。大航海時代は計算との闘いでもあったのです。天文学者は直面する天文学的計算を克服する手立てを見つけることができませんでした。彼らの計算を助けるために、ネイピアはついに新しい計算法を見つけ出す決心をします。時にネイピア44歳、1594年。その20年後の1614年、ついに人類は青天の霹靂として「対数」を手にします。第5回 ジョン・ネイピア対数誕生物語

また、日々私たちが使っている小数点(.)を使う表記はネイピアの発明です。小数点はネイピアが対数を生み出す過程で考え出した副産物だったのです。第6回 ジョン・ネイピア小数点「・」誕生物語
※小数の考え方はネイピア(1550〜1617)とほぼ同時期のシモン・ステヴィン(1548-1620) ですが19.178と表す小数を「19⓪1①7②8」のように表記していた。ステヴィンの本は1585年に出版、これはネイピアが対数表の計算を開始する1594年の9年前のことです。ネイピアはステヴィンの小数は使いづらいと判断し、もっと機能的で使いやすい小数の表記法を求め続けたのです。

発明を行うエンジニア

ネイピアはマーキストン城の城主であり、困った人を助けるために自分の才能を使う、優秀なエンジニアだったのです。
自分の領地の収穫を増やすために肥料や揚水機の研究をしたり、スペインの.侵攻を恐れて潜水艦や戦車などの兵器も数多く開発しています。これも領地内の人民を安心させるためだったのでしょう。
また、対数の計算を効率化するためにネイピアの骨と呼ばれる、かけ算や割り算などを簡単に行うための計算器具を発明しています。これは1617年(ネイピアが亡くなる年)に論文「小さな棒による計算術」としてエディンバラで発表されました。

対数表

対数表には、指定した数を底とする対数(例 底=2)、常用対数(底=10)、自然対数(底e=2.718281828…)があります。試験などで底を省略した対数表が出た場合は常用対数となります。

ネイピアが作成した対数表は不思議な底(0.9999999)だったため、使いやすい底を10にした常用対数表をネイピアの意思をついだブリッグスが作成しました。1617年に1000まで計算して出版し、1624年には1から20000までと90000から100000まで、小数点以下14桁まで計算した対数表を出版しました。1628年にブリッグスの対数表で抜けていた20,000~90,000の対数表をオランダのアドリアン・ブラックが完成させました。ただ、ブラックの対数表は10桁のものでしたが実際に利用するには十分な桁数でした。対数の発見

qiita.com

対数をとるメリット

対数のメリットは、極めてケタ数の大きい数の面倒なかけ算や割り算を、易しい足し算や引き算に直してくれることにあります。但し、求めた値はあくまで近似値であり、正確な値ではありません。
電卓や計算機のなかった昔の時代において非常に便利な計算方法でした。ピエール・シモン・ラプラスからは「天文学者の寿命を2倍にした(天文学者が一生にこなせる計算量を2倍にした)」と絶賛されるようになりました。

対数表を使った計算

131 \times 219 \times 563 \times 608 を計算する場合

10進数を底とする対数で表すと、log_{10}(131 \times 219 \times 563 \times 608) となる。
log_{10}( (1.31 \times 10^{2}) \times (2.19 \times 10^{2}) \times (5.63 \times 10^{2}) \times (6.08 \times 10^{2}) )
= log_{10}((1.31 \times 2.19 \times 5.63 \times 6.08) \times 10^{8})
ここから、かけ算が足し算になります。
= log_{10}1.31 + log_{10}2.19 + log_{10}5.63 + log_{10}6.08 + log_{10}10^{8}
= log_{10}1.31 + log_{10}2.19 + log_{10}5.63 + log_{10}6.08 + 8

ここで、  log_{10}1.31 、log_{10}2.19 、log_{10}5.63 、log_{10}6.08 の値を対数表から読み取る。
log_{10}(131 \times 219 \times 563 \times 608) \fallingdotseq 0.1173 + 0.3404 + 0.7505 + 0.7839 + 8 = 1.9921 + 8  =  0.9921 + 9 ここで、0.9921 に近い値を対数表から読み取ると、0.9921 \fallingdotseq log_{10}9.82 となる。
log_{10}(131 \times 219 \times 563 \times 608) \fallingdotseq log_{10}9.82 + 9 = log_{10}9.82 + log_{10}10^{9} = log_{10}(9.82 \times 10^{9})
こうして得られた log_{10}(131 \times 219 \times 563 \times 608) \fallingdotseq log_{10}(9.82 \times 10^{9}) の両辺を見比べると
131 \times 219 \times 563 \times 608 \fallingdotseq 9.82 \times 10^{9} = 9820000000 (実際の値は、 9820359456)

2^{29} を計算する場合

対数法則③を使います。
2^{29}は、10を底とする対数で表すと log_{10}2^{29} = 29 \times log_{10}2 となる。
log_{10}2 の値は、常用対数表から 0.3010 である。よって、log_{10}2^{29} = 29 \times 0.3010 = 8.7290 = 0.7290 + 8
常用対数の値が、0.7290 に近い真数の値を表から読み取ると、0.7290 \fallingdotseq log_{10}5.36 となる。
log_{10}2^{29} \fallingdotseq log_{10}5.36 + 8 \times log_{10}10 = log_{10}5.36 + log_{10}10^{8} = log_{10}(5.36 + 10^{8})
よって、2^{29}は、約 5.36 \times 10^{8} = 536000000 と計算できる。(実際の値は、536870912)

基本情報技術者試験の問題

ソートの計算量

計算量は一般に、O記法(オー記法、もしくはビッグオー記法と呼ぶ)を使って表します。O記法では計算量を、O(n)O(n^{2}) のような形で記述します。
性能が良い : O(1) \lt O(\log n) \lt O(n) \lt O(n\log n) \lt O(n^{2}) \lt O(n^{3}) : 性能が悪い

  • O(1)
    データ数nにかかわらず一定なので、nが2倍、3倍、10倍となっても計算量は等しいままです。
  • O(\log n)
    計算量を求める場合は底を2として考えます。
    nが2倍、3倍と増えても、計算量は1、2と増える程度なのでデータ量が多い場合に有用です。
  • O(n)
    比例なので、nが2倍、3倍と増えると、計算量も同様に2倍、3倍となります。
  • O(n \log n)
    先述のO(\log n) にnの計算量がかけられたものです。
    例えば、nが2倍になると計算量は約2.5倍、nが10倍になると計算量は約20倍、nが100倍になると計算量は約300倍になります。
  • O(n^{2})
    n個の要素を用いた総当りの組み合わせを求める場合がこのパターンに当てはまります。
    二次関数なので、nが2倍、3倍と増えると、計算量は4倍、9倍と増えます。

O記法にはいくつかのルールがある。

  • 中身は一番大きな規模だけ残す(最大次数の項以外を除く) 例 2+n+2n^{2}→2n^{2}
  • 係数は除く 例 2n^{2}→n^{2}
  • 係数は1にする

例 1 ~ Nまでの整数の総和を求めよ

  • 普通に計算する → N-1回足し算 → O(N-1) → O(N)  
  • 公式 \displaystyle S=\frac{(n+1)n}{2} を使う → 足し算と掛け算と割り算 → O(3) → O(1)

その中で対数が使われるのが下記となります。

O記法 概要 使用例
O(logN) <対数時間>
処理をひとつ行うたびに処理対象を何割か減らせるようなアルゴリズム。データ量が増えても計算時間がほとんど増えない。多くの場合、これ以上の改善はする必要はない。
ソート済み配列の二分探索
O(NlogN) <準線形、線形対数時間>
ちょっと重いO(N)程度。マージソートのように二分木でデータを分割し、かつそれらをリニアサーチするようなアルゴリズムの計算量。二分木のオーダー(logN)×リニアサーチのオーダー(N)をかけあわせてNlogNになる。
クイックソートマージソートヒープソート

二分探索の計算量

ソートされている前提で、中央の値より小さい(左側)か大きい(右側)が分かるので、次からは半分は探索する必要はなくなる。
これを2の指数で表した場合、指数の数が1つずつ減ることになるので対数にすると、O(\log n) になる。
f:id:Yaju3D:20180122012235p:plain

クイックソートの計算量

分割統治法に基づくアルゴリズムです。
f:id:Yaju3D:20180122013630p:plain
毎回半分ずつにデータが分かれたと仮定すると、平均計算量は n個 \times \log_{2} n 段 → O(n\log n) となる。
f:id:Yaju3D:20180122213121p:plain

n進数での最大値

14けたの16進数の最大値は、10進数で表すと何けたか。ここで、 log_{10}2 = 0.301 とする。

log_{10}(16^{14})
= 14 \times log_{10}(16)
= 14 \times log_{10}(2^{4})
= 14 \times 4 \times log_{10}(2)
= 14 \times 4 \times 0.301 = 16.856

繰り上げて答えは17桁になります。

身近な問題

地震の大きさや音階や星の等数や化石の年代測定や複利計算にも対数が使用します。他にも電気通信や音の単位に用いられるデシベル、音の大きさを表すホンでも使用されます。

地震のエネルギーの大きさ

地震のエネルギーの大きさを表す言葉として「マグネチュード」と言います。

マグニチュードと震度の違いは?
マグニチュード」は、地震そのものの大きさ(規模)を表すものさしです。一方「震度」は、ある大きさの地震が起きた時のわたしたちが生活している場所での揺れの強さのことを表します。

マグネチュードを M地震が発生するエネルギーを E (単位モジュール)とすると、logE =  4.8 + 1.5M という関係式があります。
この式より、E = 10^{4.8 + 1.5M} = 10^{4.8}10^{1.5M} となります。
したがって、 M が1増えるとエネルギーは10^{1.5}倍(およそ32倍)、 M が2増えるとエネルギーは10^{3}倍(およそ1000倍) になります。また、M が0.2増えるとエネルギーは10^{0.3}倍(およそ2倍) になります。

マグネチュードが2違うと1000倍で1違うと32倍って差が分かりにくいですが、x \times x = 1000 とすると x^{2} = 1000 = x = \sqrt{1000} = 31.6227  \fallingdotseq 32 となるわけです。

音階の違い

ピタゴラスの定理などで有名な古代ギリシャの数学者ピタゴラスが音階「ドレミファソラシド」を作りました。ピタゴラスが発見した音階は弦の長さで決められたものでしたが、我々が通常聞いている音階は振動数の比で決められています。

平均律音階は、1オクターブ高い音は振動数が2倍であり、その1オクターブを12音階に均等に分けたものである。
f:id:Yaju3D:20180128121348p:plain

音階 ド# レ# ファ ファ# ソ# ラ#
周波数比 2^{0}=1 2^{\frac{1}{12}} 2^{\frac{2}{12}} 2^{\frac{3}{12}} 2^{\frac{4}{12}} 2^{\frac{5}{12}} 2^{\frac{6}{12}} 2^{\frac{7}{12}} 2^{\frac{8}{12}} 2^{\frac{9}{12}} 2^{\frac{10}{12}} 2^{\frac{11}{12}} 2^{\frac{12}{12}}=2
周波数 fr fr^{1} fr^{2} fr^{3} fr^{4} fr^{5} fr^{6} fr^{7} fr^{8} fr^{9} fr^{10} fr^{11} fr^{12}
近似値 262 277 294 311 330 349 370 392 415 440 466 494 523

※周波数 f=262

r^{12} = 2 で、これを満たす r を求めると 1.06^{12} \fallingdotseq 2 となります。半音上がることに振動数を 1.06倍されている。 $$\displaystyle r = \sqrt[12]{2} = 2^{\frac{1}{12}} = 1.0594631 \fallingdotseq 1.06$$

つまり、音階も対数変換(底1.06)ととらえることができる。\log_{1.06}(周波数)=音階

星の等数

歴史上で星の明るさをランクづけしたのは、古代ギリシア天文学者ヒッパルコスです。彼は、肉眼で見えるもっとも明るい星を1等星、かろうじて見える星を6等星として、1等星~6等星までの6段階に分けました。月日が経ち、1856年にイギリスの天文学者のノーマン・ロバート・ポグソンは、ジョン・ハーシェルの研究結果を元にし1 等星の明るさは 6 等星の 100 倍であり、1 等級ごとの明るさの違いは 100^{\frac{1}{5}} 倍であると定義しました。

f:id:Yaju3D:20180128204643p:plain

$$\displaystyle r = \sqrt[5]{100} = 100^{\frac{1}{5}} = 2.51188643151 \fallingdotseq 2.51$$

つまり、星の等数は対数(底2.51)となります。

化石の年代測定

化石の年代測定ってどうやっているのか不思議でした。
これは「放射性炭素年代測定法」というのが1974年にアメリカのリビーという人が発見した方法で、炭素14の性質を利用しています。
炭素14は放射性炭素ともいわれ、電子を放出して窒素14に変わる。この崩壊によって炭素14の数が半分になるまでの期間を半減期といい5730年となっている。
生きている生物(動物、植物)はこの炭素14を体内に取り込むので、体内の炭素14の割合は大気中の割合と同じとなる。また生物が死ぬと炭素14の供給がなくなり崩壊だけが続くので発見した資料の炭素14の割合を調べることで、その資料が死んでからの年数が推定できる。

ある木管炭素14の割合を調べたら、75%に減っていた。
この場合、炭素14が1年で r 倍に現象するとして、この木簡が x 年前のものだとすると、
r^{x}=0.75 また r^{5730}=0.5

x\log r=\log 0.75 、② 5730\log r=\log 0.5
① と ② より
\displaystyle x=\frac{\log0.75}{\log r} = \frac{5730}{\log 0.5} \times \log 0.75
\displaystyle =\frac{5730(\log3 - 2\log2)}{- \log 2}
 = 5730 \times 0.4150 = 2378 年前

複利計算

サラリーローンで、1万円を1日1割の複利で2ヶ月借りる場合、
<複利計算>元利合計  = 元金 \times (1 + 利率)^{期間の数} の式となります。
1 \times (1+0.1)^{60} \fallingdotseq 305 なんと、約305万円にもなります。

これを対数で計算してみます。電卓がない状態で1.1を60回かけるんなんてうんざりですが、その場合は対数表を使えばいい。
\log_{10}x = \log_{10}1.1^{60}
対数の法則を使う
\log_{10}x = 60\log_{10}1.1
対数表では\log_{10}1.1 = 0.0414 となります。
\log_{10}x = 60 \times 0.414 = 2.484
このうち、小数部の 0.484 を対数表で見ると、3.05 です。
\log_{10}x = 2.484 = 2 + 0.484
= \log_{10}10^{2} + \log_{10}3.05
= \log_{10}100 \times 3.05
= \log_{10}305
 x = 305

自然対数

自然対数は、2.71828182845\cdots という無理数を底にした対数となります。一方、常用対数は底を10にした対数となります。
常用対数は電卓やコンピューターの登場により使用されることがなくなりましたが、自然対数はコンピューターで使用する上で標準と底となっています。これは次記事に書きます。 yaju3d.hatenablog.jp

機械学習での役割

  • かけ算を足し算または割り算を引き算にすることで計算を楽にさせる。
    \displaystyle \prod_{n=1}^{N} f(n) = \sum_{n=1}^{N}\log f(n)\prodは、かけ算を繰り返す記号
  • 機械学習では非常に大きな数を扱います。プログラムでこのような数を扱うと、オーバーフローを起こしてしまうときがあります。そのような数は、対数 をとって扱うことで、オーバーフローを防ぐことができます。例えば、100000000 = 10^{8}0.000000001 = 10^{-8} となります。
  • 機械学習では確率を使いますが、同時確率の場合には 1 以下の数の掛け算の連続になります。多い場合にはアンダーフローを起こしてしまうときがあります。そのような数は、対数 をとって扱うことで、アンダーフローを防ぐことができます。
    例としてサイコロを2回投げた場合、1回目に1の目が出て2回目に2の目が出る確率は、下記の計算となる。
    \displaystyle \frac{1}{6} \times \frac{1}{6} = \frac{1}{36}
  • 対数は単調増加関数であるため、ある関数 f(x) があって f(x) を最小にする値を求めた場合、対数をとった \log f(x) でも最小の値は同じになります。また、最大値を求める場合でも同じです。
    f(x)=(x-1)^{2}+2は、x=1の時、最小値をとる
    \log f(x)=\log ((x-1)^{2}+2) も、x=1の時、最小値をとる

単調増加関数

単調増加な関数は、任意な値  x1 と x2 があったとき 、 x1 \lt x2 ならば  f(x1) \lt f(x2) となるような関数  f(x) であるいうことです。
例えば、  x1=3、x2 = 5 x1 \lt x2 が成り立ちます。この関係性は対数に直しても  \log x1 \lt \log x2 が成り立ちます。

「サザエさんのじゃんけん データ分析」の2017年の結果

はじめに

明けましておめでとうございます。

昨年も東芝に暗雲が立ち込め、東証2部に降格して日経平均から外れ、ついには18年3月末にサザエさんからのスポンサー契約を降板する方向で調整とあいなりました。
さて、サザエさんのじゃんけんを長年記録しデータをWebスクレイピングさせて頂いた「サザエさんジャンケン学」が2017年6月25日を以て終了されていました。データを取得した際に7月以降がなかったので、あれっと思って見たらそういうことでした。長い間お疲れ様でしたm(_ _)m
www.asahi-net.or.jp

さてさて、2017年のサザエさんのじゃんけん結果はどうなったでしょう。 ちなみに、2016年のサザエさんのじゃんけん結果は、27勝11敗12分(勝率0.711)でした。

人工知能による予測化を断念

本来はDeepLearningを使用して予想をする予定でしたが、まだ勉強中のままです。あと少しだと思うので今年中にはなんとかなると思います。
昨年、R言語からPythonに切り替えました。
qiita.com

Python使うに際にはいつも Jyupter notebook だったので、この為に久しぶりに Visual Studio Code で Python を実行しようとしてバージョンアップ(1.19.1)したらタスク実行の設定がいろいろ変わって動かなくて、四苦八苦しながらなんとか動かせるようにしました。下記の記事を修正してあります。 yaju3d.hatenablog.jp

次の手の予測アルゴリズム

  • チョキが多いので、グー > チョキ > パーの優先順位とする
  • 前回と違う手を出すので、上記の優先順位で勝手を選ぶ
  • 二手前と一手前が違う手なら、残りの手を出すので勝手を選ぶ
  • 三手の中に同手がある場合、 残りの手を出すので勝手を選ぶ
  • 二手前と一手前が同じ手なら、勝手を出すので負手を選ぶ

2017年の勝敗結果

年月 サザエさんの手 予想の手 勝敗結果
01月01日 休み
01月08日 チョキ グー 勝ち
01月15日 チョキ グー 勝ち
01月22日 パー パー 引き分け
01月29日 グー パー 勝ち
02月05日 グー グー 引き分け
02月12日 パー グー 負け
02月19日 チョキ グー 勝ち
02月26日 チョキ パー 負け
03月05日 グー パー 勝ち
03月12日 パー チョキ 勝ち
03月19日 休み
03月26日 チョキ グー 勝ち
04月02日 チョキ パー 負け
04月09日 グー パー 勝ち
04月16日 パー チョキ 勝ち
04月23日 パー グー 負け
04月30日 チョキ グー 勝ち
05月07日 グー パー 勝ち
05月14日 パー チョキ 勝ち
05月21日 チョキ グー 勝ち
05月28日 グー パー 勝ち
06月04日 チョキ チョキ 引き分け
06月11日 パー チョキ 勝ち
06月18日 グー パー 勝ち
06月25日 グー グー 引き分け
07月02日 チョキ グー 勝ち
07月09日 パー チョキ 勝ち
07月16日 グー パー 勝ち
07月23日 パー グー 負け
07月30日 チョキ グー 勝ち
08月06日 パー パー 引き分け
08月13日 グー パー 勝ち
08月20日 チョキ グー 勝ち
08月27日 グー チョキ 負け
09月03日 パー チョキ 勝ち
09月10日 パー グー 負け
09月17日 チョキ グー 勝ち
09月24日 グー パー 勝ち
10月01日 チョキ チョキ 引き分け
10月15日 パー チョキ 勝ち
10月22日 グー パー 勝ち
10月29日 休み
11月05日 チョキ グー 勝ち
11月12日 チョキ チョキ 引き分け
11月19日 グー チョキ 負け
11月26日 パー チョキ 勝ち
12月03日 パー グー 負け
12月10日 チョキ グー 勝ち
12月17日 グー パー 勝ち
12月24日 パー チョキ 勝ち

結果は、32勝9敗7分(勝率0.780)となりました。

ちなみに、サザエさんじゃんけん研究所 公式ウェブサイトサザエさんの手の予想と勝負結果(2017年)が29勝8敗11分(勝率0.783)でした。

今回は勝数では上回ったのですが、勝率では僅差で負けました。 勝率の計算は、「勝ち / (勝ち + 負け)」で行っているのですが、負けの1つが響いたわけです。

【2017/01/09追記】
2017年冬版 サザエさんじゃんけん白書によるとクール(四半期)の初回(1月、4月、7月、10月の初回)はチョキが出やすいとのことです。もし、これを取り入れた場合、04月02日(負け)と10月01日(引き分け)なので、34勝8敗6分(勝率0.809)になるかな。

データ分析 研究所公式
2013 24勝13敗12分(勝率0.649) 25勝9敗17分(勝率0.735)
2014 30勝10敗11分(勝率0.750) 30勝9敗12分(勝率0.769)
2015 32勝9敗9分(勝率0.780) 33勝9敗8分(勝率0.785)
2016 27勝11敗12分(勝率0.711) 22勝13敗15分(勝率0.628)
2017 32勝9敗7分(勝率0.780) 29勝8敗11分(勝率0.783)

スライド

2013年に静岡Developers勉強会で機械学習を学び、2014年1月にネタとしてSlideShareに公開しました。

サザエさんのじゃんけん データ分析 from yaju88

最後に

今年中にはTensorFlowを使って人工知能ディープラーニングで予測手を作りたいと思います。まだ組むだけの理解度(n-gramモデル)が足りないためですが、あと少しで理解度が進むはずです。

【2018/01/07 追記】
r_stdさんが、機械学習を用いて検証してくれました。2016年に統計検定準1級に合格している方で幾つかの手法を使っています。その中で、naive bayesで33勝9敗6分と好成績を残しています。タイトルが①なので次回も期待しています。
r-std.hatenablog.com

ディープラーニング(深層学習)を理解してみる(活性化/損失関数)

はじめに

前回、勾配降下法の計算方法をPythonで実際に組んでみて、TensorFlowで実行した結果と同じになりました。
yaju3d.hatenablog.jp

今回は、活性化関数/損失関数を軽くまとめてみます。 というのも、前回の勾配降下法の計算確認をした際に活性化関数を使ってなかったことに疑問を持ったからです。

2017年に始めたこのシリーズは1ヶ月に1記事のペースになってしまったのですが、来年はペースを上げたいところ。

来年から誤差伝播法、畳込みニューラルネットワーク(CNN)、リカレントニューラルネットワーク(RNN)をやっていきます。

下記5つの本(書籍版とKindle版)を、これまでに参考にさせて頂きました。
    

活性化関数

活性化関数は入力信号の総和を出力信号に変換する関数のことです。どのように活性化するか(どのように発火するか)ということを決定する役割があります。
人工知能の本を読むと、活性化関数としてステップ関数、シグモイド関数、ソフトマックス関数などが出てきます。
前回の勾配降下法の計算確認をした際に、先述した活性化関数は1つも使っていませんでした。
調べて見たところ、参考にした本には「活性化関数は恒等関数を使います」と書かれていました。

ゼロから作るDeepLearning
3.5 出力層の設計
ニューラルネットワークは、分類問題と回帰問題の両方に用いることができます。ただし、分類問題と回帰問題のどちらに用いるかで、出力層の活性化関数を変更する必要があります。一般的には、回帰問題には恒等関数を、分類問題ではソフトマックス関数を使います。
分類問題とは、データがどのクラスに属するか、という問題です。(中略) 一方、回帰問題は、ある入力データから、(連続的な)数値の予測を行う問題です。

今回はリンゴとミカンの値段を予測するということで回帰問題となるため、活性化関数は恒等関数を使ったわけです。

「回帰分析」という言葉の誕生
科学史から最小二乗法 (回帰分析) を説明してみる

活性化関数の種類

活性化関数でも使用する場所(層)があります。
中間層(隠れ層)での活性化関数は、通常はシグモイド関数やReLUなどが用いられ、出力層での活性化関数は、回帰問題なら恒等関数、分類問題の2値分類ならシグモイド関数、分類問題の多値分類ならソフトマックスなどが用いられます。

代表的な活性化関数です。

  • Step (ステップ関数)
  • Linear (線形関数)
  • Identity (恒等関数)
  • Sigmoid (シグモイド関数 ロジスティクス関数)
  • Tanh (双曲線正接関数 hyperbolic function)
  • Hard Sigmoid
  • Hard Tanh
  • ReLu (ランプ関数 正規化線形関数 Rectified Linear Unit)
  • Leaky ReLU
  • PReLU(Parametrized ReLU)
  • RReLU
  • SReLU
  • ELU
  • SELU
  • Probit
  • LeCun Tanh
  • ArcTan
  • SoftSign (ソフトサイン関数)
  • Softplus (ソフトプラス関数)
  • Softmax (ソフトマックス関数)
  • Signum
  • Bent Identity
  • Symmetrical Sigmoid
  • Log Log
  • Gaussian
  • Absolute
  • Sinusoid
  • Cos
  • Sinc
  • Swish
  • Gumbel-Softmax
  • eLU (Exponential Linear Units)
  • Thresholded ReLU
  • Maxout (区分線形凸関数)

次のサイトで8割くらいが可視化されています。
dashee87.github.io

恒等関数(Identity)

恒等関数は言葉こそ難しいですが、単純に入ってきたものに対して何も手を加えずに出力する関数となります。 「恒」の意味が「いつも変わらない。」となっています。ソフトマックス関数を比べると分かりやすい。
f:id:Yaju3D:20180104021747p:plain

def koutou(a):
  return a

ステップ関数(Step)

ステップ関数は入力に対してある閾値を境に階段(ステップ)のように出力が1か0か決まる関数となります。
f:id:Yaju3D:20170603024904p:plain

入力した値が0以下のとき0になり、0より大きいとき1になるステップ関数の実装例

def step_function(x):
  if x>0:
    return 1
  else:
    return 0

ニューラルネットワークは学習をする際に微分を含む計算を行うため、微分するとゼロになるステップ関数は都合が悪いことからシグモイド曲線(sigmoid)などが使用されている。

線形関数(Linear)

線形関数は単純パーセプトロンには使えますが、多層パーセプトロン(ニューラルネットワーク)には使えません。
線形関数で層を増やしても多層化には貢献しないのです。例えば任意の関数 y = f(x) で構成される層に、線形関数 g = cx の層を重ねたとしても、それは y = f(cx) で構成される単層パーセプトロンとやってることは同じになるためです。

def linear_function(x, w, b):
    return w * x + b

シグモイド関数(Sigmoid)

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

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

def sigmoid(x):
  return 1 / (1+np.exp(-x))

Sigmoid関数で値が大きい場合の対応
数値計算で必要な数式変形について – はむかず!

双曲線正接関数(Tanh)

双曲線正接関数は、シグモイド関数の線形変換であり、正にも負にも値を取り、原点を通ることから後に採用されるようになった。
f:id:Yaju3D:20180104203510p:plain

def tanh(x):
    return numpy.tanh(x)

ランプ関数(ReLU)

ランプ関数は、入力した値が0以下のとき0になり、1より大きいとき入力をそのまま出力します。
シグモイド関数や双曲線正接関数は、原点から遠ざかる(値の絶対値が大きい)ほど、勾配が無くなるため、一度ユニットが大きな値を持ってしまったら学習が停滞する問題がある。ランプ関数では勾配消失問題が経験的に解消されることが知られている。
ReLUの派生として、Leaky ReLU、PReLU、RReLU、SReLU がある。
f:id:Yaju3D:20180104202547p:plain

def relu(x):
  return np.maximum(0, x)

区分線形凸関数(Maxout)

他のディープラーニングの手法とは異なり、活性化関数自体を学習するという、少し特殊な手法です。実験による精度はReLU より表現力が高いです。多数の線形関数のmax(任意の閾値関数を近似)するため、計算時間が増えます。
数式で書き下す Maxout Networks

f:id:Yaju3D:20180106130542p:plain

ソフトマックス関数(Softmax)

ソフトマックス関数は、中間層(隠れ層)ではなく出力層にて分類問題の多値分類で使用される。

ソフトマックス関数の特徴は、すべての出力の総和が1に調整されることだ。これにより、各出力を確率として考えることができる。例えば入力画像が①犬なのか、②猫なのか、③パンダなのかを判定するニューラルネットワークを設計したとする。ソフトマックス関数を介する前①0.3、②2.9、③4.0という「真である可能性の高さ」を持っていた場合、単に「画像はパンダである」と答えることができる。しかし、ソフトマックス関数を介すると出力は①0.018、②0.245、③0.737というように出て、「画像は73.7%の確率でパンダである。」と言える。
designcodelab

\displaystyle y_k = \frac{exp(a_k)}{\sum_{i=1}^n exp(a_i)}

def softmax(a):
  exp_a = np.exp(a)
  sum_exp_a = np.sum(exp_a)
  y = exp_a / sum_exp_a
  return y
オーバーフロー対策

上記コードの実装は理論上は正確だが、実際に動かした場合に入力信号の値が大きいと指数関数の値が大き過ぎてオーバーフローしてしまう。 e^{10} は、20,000を超え、 e^{100} は 0 が40 個以上も並ぶ大きな値になり、e^{1000} では桁が大きすぎてコンピュータが無限大(inf)と解釈してしまう。このオーバーフローを避けるには、計算結果の理論値を変えずに計算途中の数字の絶対値を小さくする工夫が要る。

改善したのが次の数式となります。
\displaystyle y_k = \frac{Cexp(a_k)}{C\sum_{i=1}^n exp(a_i)}

\displaystyle \quad = \frac{exp(a_k + logC)}{\sum_{i=1}^n exp(a_i + logC)}

\displaystyle \quad = \frac{exp(a_k + C')}{\sum_{i=1}^n exp(a_i + C')}

任意の定数 C を分子と分母の両方に掛けています(分母と分子の両方に同じ定数を乗算しているため、同じ計算を行っていることになります)。そして、指数関数(exp)の中に移動させ、logC とします。最後にlogCC’ という別の記号に置き換えます。
ソフトマックスの指数関数の計算時には何らかの定数を足し算(もしくは、引き算)しても結果は変わらない、ということです。
定数の値 C としては入力信号の中の最大の値を用いることが一般的。

def softmax(a):
  c = np.max(a)
  exp_a = np.exp(a – c)
  sum_exp_a = np.sum(exp_a)
  y = exp_a / sum_exp_a
  return y

改善前はnanでオーバーフローになるが、配列の中の最大値 1010 を引いた値で計算することでオーバーフローなく計算ができるようになる。

>>> a = np.array([1010, 1000, 990])
>>> np.exp(a) / np.sum(np.exp(a)) # ソフトマックス関数の計算
>>> array([  nan,  nan,  nan])
>>> c = np.max(a) # 1010
>>> a - c
array([  0,  -10,  -20])
>>> 
>>> np.exp(a - c) / np.sum(np.exp(a - c))
array([   9.99954600e-01,   4.53978686e-05,   2.06106005e-09])

損失関数(コスト関数)

機械学習では学習時に、いかに答えに近い値になるように重みパラメータを調整(必要な補正量を定量的に示す)させます。その部分を担うのが損失関数となります。そして損失関数の最小値を探すことが学習のゴールとなる。
損失関数を設定する理由は、認識精度を指標するとパラメータの微分がほとんどの場所で 0 (動かなくなる)になるから。

自分で一から組んでみた際に、シグモイド関数と交差エントロピーの使い方で頭の中がごちゃごちゃしてたけど、勾配降下法の計算する上では損失関数は使用しない。ってことでスッキリした感じがある。

二乗誤差(mean squared error)

\displaystyle E = \frac{1}{2}\sum_{k=1} (y_k - t_k)^2
def mean_squared_error(y,t):
    return 0.5 * np.sum((y-t)**2)

線形回帰で二乗和を2で割る理由について - デジタル・デザイン・ラボラトリーな日々

交差エントロピー誤差(cross entropy error)

交差エントロピー誤差は、正解の選択肢に対してのみ、出力と理想的な値との距離を計る。
計算式には全ての値が含まれるが、正解ラベルがone-hot表現されていれば不正解となる選択肢の項は自動的に全て消える。

\displaystyle E=\sum_{k=1} - t_k \log y_k
def cross_entropy_error(y, t) :
    delta = le-7
    return -np.sum(t*np.log(y+delta))

例えば出力 y はクラスが5つの場合次のような列になります。
[0.1, 0.3, 0.8, 0.2, 0,1]   正解のクラスが、1つ目は3(0.8)で2つ目は2(0.3)であるとき、教師データはそれぞれ次のようになります。
one-hot [0, 0, 1, 0, 0] ラベル [3]

出力 y と one-hot表現を掛けた場合
[log(0.1), log(0.3), log(0.8), log(0.2), log(0,1)] と one-hot [0, 0, 1, 0, 0]
3つ目の値以外は 0 を掛けるために 0 になってしまい、log (0.8) だけが出てきて、計算結果は -log(0.8) になります。

損失関数なので、例えばニューラルネットワークA(0.8)とニューラルネットワークB(0.05)のとき、同じ正解でも結果が大きい値(0.8)になった場合、重みパラメータを補正する必要があることを示している。

最後に

前回、勾配降下法の計算確認にて損失関数を求めた、正解に近づくにつれ値が小さくなること分かったが、その計算結果を使って何かしたわけではない。次回の誤差伝播法では損失関数の計算結果を使用するんだと思う。

ディープラーニング(深層学習)を理解してみる(勾配降下法:計算確認)

はじめに

前回の続きです。 yaju3d.hatenablog.jp

前回の計算が本当に合っているのか、Pythonを使って実証してみたいと思います。

プログラム

重みの更新

①現在の重みで推測値を求める

import numpy as np
a = np.array([10, 20])
b = np.array([[1,3,5],[3,1,7]])
print(np.dot(a,b))

[ 70, 50, 190]

全体の誤差を求める。
二乗誤差は、mean_squared_error の名前です。

def mean_squared_error(y, t):
    return 0.5 * np.sum((y - t)**2)

y = np.array([70, 50, 190])
t = np.array([190, 330, 660])

print(mean_squared_error(y, t))

156850.0

②勾配\Delta Eを計算する
勾配は、gradient の名前です。転置は x.T とTを付けるだけなんですね。

def gradient(y, t, x):
    return np.dot((y - t),x.T)
    
t = np.array([190, 330, 660])
y = np.array([70, 50, 190])
x = np.array([[1,3,5],[3,1,7]])

print(gradient(y, t, x))

[-3310 -3930]

③重みを更新する
②で求めた勾配\Delta E を用いて、現在の重みw を更新します。学習係数η は 0.02 としています。

def weight(w, lr, gd):
    return w - lr * gd

t = np.array([190, 330, 660])
y = np.array([70, 50, 190])
x = np.array([[1,3,5],[3,1,7]])
w = np.array([10, 20])

print(weight(w, 0.02, gradient(y, t, x)))

[ 76.2 98.6]

2回目の①現在の重みで推測値を求める

a = weight(w, 0.02, gradient(y, t, x))
b = np.array([[1,3,5],[3,1,7]])
print(np.dot(a,b))

[ 372. 327.2 1071.2]

全体の誤差を求める。
二乗誤差は、mean_squared_error の名前です。

y = np.dot(a,b)
t = np.array([190, 330, 660])

print(mean_squared_error(y, t))

101108.64

というのを繰り返す。

import numpy as np

# 二乗誤差を求める
def mean_squared_error(y, t):
    return 0.5 * np.sum((y - t)**2)

# 勾配を求める
def gradient(y, t, x):
    return np.dot((y - t),x.T)

# 重みを求める
def weight(w, lr, gd):
    return w - lr * gd

# 教師データ
t = np.array([190, 330, 660])
# 重み
w = np.array([10, 20])
# 入力データ(個数)
x = np.array([[1,3,5],[3,1,7]])
y = None
for i in range(100):
    if y is not None:
        # 勾配を求める
        grad = gradient(y, t, x)
        # 重みを求める
        w = weight(w, 0.02, grad)
    # 推測値を求める
    y = np.dot(w, x)
    # 誤差を求める
    l = mean_squared_error(y, t)
    if (i + 1) % 10 == 0:
        print("step=%3d, a1=%6.2f, a2=%6.2f, loss=%.2f" % (i + 1, w[0], w[1], l))
print("Estimated: a1=%6.2f, a2=%6.2f" % (w[0], w[1]))
step= 10, a1= 78.84, a2= 48.86, loss=4531.39
step= 20, a1= 89.41, a2= 32.85, loss=564.82
step= 30, a1= 94.92, a2= 27.91, loss=264.21
step= 40, a1= 97.30, a2= 26.05, loss=217.63
step= 50, a1= 98.28, a2= 25.30, loss=209.89
step= 60, a1= 98.68, a2= 25.00, loss=208.59
step= 70, a1= 98.84, a2= 24.88, loss=208.38
step= 80, a1= 98.91, a2= 24.83, loss=208.34
step= 90, a1= 98.94, a2= 24.81, loss=208.33
step=100, a1= 98.95, a2= 24.80, loss=208.33
Estimated: a1= 98.95, a2= 24.80

実行結果
リンゴ 98.95 ≒ 99、ミカン 24.80 ≒ 25 → 99 x 2 + 25 x 4 ≒ 297

以前、TensorFlowで組んだ際の結果とほぼ同じになったのでこれでいいかな。
リンゴ 98.81 ≒ 99、ミカン 24.90 ≒ 25 → 99 x 2 + 25 x 4 ≒ 297
yaju3d.hatenablog.jp

上記サイトでTensorFlowで組んだのに近付けるために、train_x と train_y を縦ベクトル に変更してみました。

f:id:Yaju3D:20160419005112p:plain f:id:Yaju3D:20160419002003p:plain

import numpy as np

# 二乗誤差を求める
def mean_squared_error(y, t):
    return 0.5 * np.sum((y - t)**2)

# 勾配を求める
def gradient(y, t, x):
    return np.dot((y.T - t.T), x)

# 重みを求める
def weight(w, lr, gd):
    return w - lr * gd

train_x = np.array([[1., 3.], [3., 1.], [5., 7.]])
train_y = np.array([190, 330, 660]).reshape(3, 1)
y = None
for i in range(100):
    if y is not None:
        # 勾配を求める
        grad = gradient(y, train_y, train_x)
        # 重みを求める(横ベクトルを一次元に変換)
        w = weight(w, 0.02, grad).reshape(-1,)
    else:
        # 初期重み
        w = np.array([10, 20])

    # 推測値を求める
    y = np.dot(w, train_x.T)
    # 誤差を求める
    l = mean_squared_error(y, train_y)
    if (i + 1) % 10 == 0:
        print("step=%3d, a1=%6.2f, a2=%6.2f, loss=%.2f" % (i + 1, w[0], w[1], l))
print("Estimated: a1=%6.2f, a2=%6.2f" % (w[0], w[1]))