-
Notifications
You must be signed in to change notification settings - Fork 3
/
epilogue.html
180 lines (167 loc) · 11.2 KB
/
epilogue.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="common.css" />
<title>おわりに - なぜ機械学習はうさん臭く感じられるのか?
/ 真面目なプログラマのためのディープラーニング入門</title>
<style><!--
--></style>
<body>
<div class=nav>
<a href="index.html">< もどる</a>
</div>
<h1>おわりに - なぜ機械学習はうさん臭く感じられるのか?</h1>
<p>
本講座では計8回にわたり、ディープニューラルネットワークの原理と実装について
説明してきた。ニューラルネットワークの原理は基本的には
勾配降下法であり、その基盤となっているのが関数の微分可能性である。
ニューラルネットワークにはさまざまな形態が存在するが、
画像処理・画像認識の場合は畳み込みニューラルネットワークが非常に
有効であることがわかっている。また、ニューラルネットワークの
出力形式や損失関数を変えることにより、ニューラルネットワークが
物体検出や奥行き推定など、さまざまなタスクに利用可能であることを紹介した。
<p>
さて、本講座は「真面目なプログラマのための」ディープラーニング入門、
と銘打っている。真面目なプログラマとは何か? 諸説いろいろあるだろうが、
多くのプログラマは、ソフトウェア開発において
<strong>仕様の明確さ</strong>や、
<strong>システムの効率・堅牢性</strong>、そして
<strong>保守のしやすさ</strong>といったものを
追求するであろう。このようなプログラマにとって、
ニューラルネットワーク (および機械学習全般) が
とっつきにくいと感じる理由はいくつかある:
<dl>
<dt> 仕様のわかりにくさ
<dd>
たいていのソフトウェアは
「正しい挙動は明確に定義可能である」という想定のもとに作られている。
間違ったコードはエラーを発生させたり、間違った結果を出力するので、
プログラマの多くは、バグは (十分な時間さえあれば)
いずれ発見・修正が可能であろうと考えている。
しかし機械学習の場合、この想定は成り立たない。
なぜなら機械学習のモデルは完璧ではないため、
正しいコードがつねに正しい解を出すとは限らないからだ。
逆に間違ったコードが「正しそうな解」を出すこともある。
たとえば以下の例は <code>backward()</code> メソッドをわざと間違って書いてある
(<a href="./lec3/index.html#learn-gd">第3回で使われた数式</a>を正確に実装していない)。
しかしこれを使って訓練しても損失は少しずつ低下し、
エラーも出ないため、すぐにはバグに気づかない:
<pre>
<span class=comment># 間違ったレイヤーの実装 (でも、なんとなく動作する)</span>
class BuggyLayer:
...
def backward(self, delta):
<span class=comment># self.y が計算されたときのシグモイド関数の微分を求める。</span>
ds = [ y1*(1-y1) for y1 in self.y ]
<span class=comment># 各偏微分を計算する。</span>
for i in range(self.nout):
for j in range(self.nin):
self.dw[i][j] += <span class=err>delta[i] * ds[i]</span>
for i in range(self.nout):
self.db[i] += delta[i] * ds[i]
return <span class=err>self.x</span>
</pre>
<dt> 仕様の流動性
<dd>
上のようなバグはアルゴリズム (数式) の単純な実装ミスであり、まだ修正は比較的容易である。
しかし機械学習においてはこのアルゴリズム自体の正当性が曖昧なため、
たとえそれを正しく実装できたとしても、真の意味で「正しいアルゴリズム」かどうかは
わからない。なぜなら、機械学習における「正しさ」とは、
せいぜい「実行してみて精度が高いもの」という程度の定義しかないからだ。
たとえば、あるニューラルネットワークで畳み込みレイヤーをいくつ重ねればよいのか?
という疑問に対しては、決まった規則や答えがなく、
試行錯誤によって答えを見つけるしかない。
さらに、ニューラルネットワークの世界では、研究者にとっても
「なぜこれで精度が上がるのかよくわからない」方法や、
「どちらが本当にいい方法なのかよくわからない」テクニックが数多く存在する。
そしてこれらの意見は数年のスパンで変わることがある。
例をあげると:
<ul>
<li> 2014年当時、<a href="./lec7/index.html#dropout">ドロップアウト</a>は
画期的な技術としてもてはやされたが、いまではあまり使われなくなった。
<li> <a href="./lec7/index.html#batchnorm">バッチ正規化</a>の項では
「活性化関数の直前にバッチ正規化レイヤーをはさむ」と解説しているが、実は
<a target="_blank" href="https://www.reddit.com/r/MachineLearning/comments/67gonq/d_batch_normalization_before_or_after_relu/">バッチ正規化レイヤーは活性化関数の後のほうがよい</a>、という意見もある。
<li> 畳み込みネットワークでは Max pooling (あるいは avg pooling) を使っても、
ストライドを使っても画像を縮小できるが、
<a target="_blank" href="https://arxiv.org/abs/1412.6806">精度はほぼ同じなので常にストライドを使うべき</a>、
という意見がある。
</ul>
などである。
<dt> 品質の悪い実装
<dd>
以上のように、多くのニューラルネットワークの仕様は流動的なので、
開発者の多くは「何が正しいのか」よくわからないままに
開発することになる。すると、試行錯誤の跡がそのままコードに残されることが多い。
つまり無駄なコードや効率の悪いロジックが放置されることになる。
しかも文書化もおざなりで、十分なテストケースも提供されていないことが多い。
「正しい仕様」がはっきりしない状況では、
こうしたコードを適切にリファクタリングすることは難しく、
したがってシステムの効率化や信頼性の向上といった作業も難しくなる。
<dt> フレームワークの不安定さ
<dd>
まさに上に述べたことを体現するかのように、
多くの機械学習フレームワークは「堅牢なソフトウェア」とは
言いがたいものが多い。たとえば PyTorch のモデルを正しく動かすためには、
以下の要素がすべて揃っている必要がある:
<ul>
<li> 正しい PyTorchのバージョン
<li> 正しい Pythonのバージョン
<li> 正しい CUDAのバージョン
</ul>
よくできたフレームワークというものは、たいてい後方互換性が保たれるように
作られている。したがって、バージョンの差はそれほど問題とはならない。
しかし機械学習フレームワークは後方互換性が保たれない変更が
されることがしばしばあり (最近は安定してきている)、
それぞれのフレームワークが「特定のバージョンのライブラリ」に
依存していることが多い。また、個々の構成要素 (PyTorch/Python/CUDA) は
それぞれ勝手なタイミングで更新されるため、
うっかりバージョンアップしたら以前のモデルが
動かなくなった、ということが頻繁にある。
(本講座の最初で
<a href="./lec1/index.html#setup-python">「Python の最新バージョンを使わないように」</a>と
警告したのはこのためである。)
<dt> 標準化の欠如、コミュニティの分断
<dd>
本講座では機械学習フレームワークとして PyTorch を使ってきたが、
もうひとつ有名なフレームワークとして TensorFlow がある。
本講座で PyTorch を使っているのは、ひとえに筆者の好みによるものである。
機械学習フレームワークは日々、新しいものが現れているが、
その多くは似たような機能をもっており、車輪の再発明が多い。
また、公開されているモデルや論文などの実装はたいてい
ひとつのフレームワークでしか動作しないため、
「別フレームワークによる同一アルゴリズムの実装」を作るという
試みがさかんに行われている。さらに (本講座も含めて)
「学習のためにあえて自力で作製した」実装が非常に多く出回っており、
たとえば GitHub で
<a target="_blank" href="https://github.com/search?q=yolo+pytorch">PyTorch で実装した
YOLO</a>
を検索してみると、1,000以上のリポジトリがヒットする
(TensorFlow ではさらに多くの実装が存在するようだ)。
YOLO のような有名なアルゴリズムさえ「定番の実装」が存在しない状態では、
これを基盤としたシステムを構築することにはリスクを伴う。
ONNX形式のような試みはなされているが、
まだそれほど普及しているとはいえない。
このような標準化の欠如、およびそれによる
コミュニティの分断は現在の機械学習における大きな課題である。
</dl>
<p>
まとめると、機械学習 (および、それをとりまく環境) は、
<strong>真面目なプログラマの足を引っぱる要素が多い</strong>のである。
とくにニューラルネットワークは上記のように不確定な要素が多く、
率直にいって使わないに越したことはない。
結局のところ、本講座は「真面目なプログラマが、
ニューラルネットワークという不安定要因からシステムを守るための予備知識」
と言えるかもしれない。機械学習・ニューラルネットワークはたしかに
役に立つ技術といえそうだが、それは現代の巨大な
情報システムの中のごく一部にすぎない。ソフトウェア全体にわたる
総合的な知識なしには、機械学習が真の意味で世の中を良くすることは
ありえないのだ。ソフトウェアに対して「真面目」でありつつ、
なおかつ機械学習について語れるエンジニアが今後必要とされていくであろうと思われる。
<hr>
<div class=license>
<a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"><img alt="クリエイティブ・コモンズ・ライセンス" style="border-width:0" src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png" /></a><br />この作品は、<a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">クリエイティブ・コモンズ 表示 - 継承 4.0 国際 ライセンス</a>の下に提供されています。
</div>
<address>Yusuke Shinyama</address>