【読書メモ】Deep Learningを学ぶ〜推論の実装をバッチ処理化する〜Part 15
最初に
本記事は以下の本から得た知見を自分用のメモも兼ねてまとめた記事となります。ただし本の内容をそのまま書き写ししているわけではなく、ある程度自身で調べたものや感想が混じっています。ご了承ください。
ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装
また、本記事は株式会社よむネコのプロジェクトの業務内のお時間を頂いて蓄積した知見となります。
以下会社URLです。
今回は、前回作成した、MNISTの手描き文字の推論をバッチ処理にしつつ、バッチ処理自体がどういったものかを学んでいきます。
バッチ処理とは?
n個のデータをまとめて計算したいと考えた場合に、入力の数値をnxにすると行列の積の計算を一度で行うことができます。例えば、100個のデータを一気に計算したい場合は、前回の例だとX=784, N=100, N×Xを入力として与えることでまとめて計算ができます。この場合、出力データの形状はYが10クラスだとするとN×Yとなります。与えたデータ分が一度に出力されます。
全ての訓練データ(n個)を1回で学習することを、1エポック(epoch)と数えます。
また、まとまりのある入力データの束をバッチと呼び、バッチをまとめて処理することをバッチ処理と呼びます。バッチに含まれるサンプル数のことをバッチサイズと呼びます。このバッチサイズは基本的に学習中ずっと一定です。
どのようなタイプの処理を行うかはバッチサイズに寄ります。これは追々確認していこうと思います。
なぜバッチ処理?
バッチ単位で推論処理を行うと計算が高速化できる。これは、NumPyなどの数値計算を扱うライブラリの殆どが大きな配列の計算を効率よく処理できるように最適化しているからです。
実際に前回の内容をバッチ処理に置き換える
前回の実装内容が以下の通りです。
x, t = get_data() network = init_network() # ニューラルネットワークが正解したカウント変数 accuracy_cnt = 0 for i in range(len(x)): y = predict(network, x[i]) # 最も確率の高い要素のインデックスを取得 p = np.argmax(y) # ニューラルネットワークが予測した答えと正解ラベルを比較 if p == t[i]: accuracy_cnt += 1 # 一致していれば正解数をインクリメント # 正解数 / xの要素数 = 正解率 print("Accuracy:" + str(float(accuracy_cnt) / len(x)) # 結果:0.9352 つまり93.52%
これをバッチ処理にすると以下のようになります。
x, t = get_data() network = init_network() accuracy_cnt = 0 # バッチサイズ batch_size = 100 # range(start, end, step=1ループ当たりstepの値分足されていく) for i in range(0, len(x), batch_size): # xのデータのi番目からi+batch_size番目までを取り出す. x_batch = x[i:i+batch_size] y_batch = predict(network, x_batch) # axis=1 とすると100*10の配列の中で1次元の要素ごとに # 最大値のインデックスを見つける. # [[0.1, 0.5, 0.4], [0.7, 0.2, 0.1], [0.8, 0.1, 0.1]] なら # [1 0 0] p = np.argmax(y_batch, axis=1) # p = [1, 0, 0], t = [1, 1, 0] なら [True, False, True] # cnt += np.sum(p == t) accuracy_cnt += np.sum(p == t[i:i+batch_size]) # 正解数 / xの要素数 = 正解率 print("Accuracy:" + str(float(accuracy_cnt) / len(x))
最後に
これでバッチ処理として高速に処理が行えるようになりました。次はニューラルネットワークの学習に関する章となります。頑張って進めていこうと思います。
【読書メモ】Deep Learningを学ぶ〜学習済みパラメータを使用して手書き文字を推論〜Part 14
最初に
本記事は以下の本から得た知見を自分用のメモも兼ねてまとめた記事となります。ただし本の内容をそのまま書き写ししているわけではなく、ある程度自身で調べたものや感想が混じっています。ご了承ください。
ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装
また、本記事は株式会社よむネコのプロジェクトの業務内のお時間を頂いて蓄積した知見となります。
以下会社URLです。
今回は、実際に手書き数字を認識するような、分類問題を扱ってみたいと思います。尚、学習済みのパラメータを使用します。
推論処理の実装
ニューラルネットワークの推論処理は「順方向伝播(forward propagation)」とも言われます。
機械学習でも問題を解く手順は「学習」と「推論」がありますが、ニューラルネットワークで問題を解く場合にも、まずは学習(訓練)データを使って重みパラメータの学習をし、推論時には学習済みパラメータを使って分類問題を扱います。
今回はMNIST(Mixed National Institute of Standards and Technology database:エムニスト)というデータセットを利用します。MNISTは機械学習分野の中でも最も有名なデータセットの一つで、論文として発表するような研究での実験用データとしてもよく利用されているようです。
MNISTには手書き数字画像のデータ60,000枚と、テスト画像10,000枚+その画像に書かれた数字の正解ラベルデータがセットになっています。
この画像データは28×28のグレー画像で、各ピクセルは0〜255までの値を取ります。それぞれの画像にそれぞれの数字に対応するラベルが与えられています。
"MNISTデータセットDL→画像データをNumPy配列へ変換"までをまず行います。
MNISTデータセットのDLを行う
まずはGithubでこの本のソースコードをCloneかzipで落としてきます。
https://github.com/oreilly-japan/deep-learning-from-scratch
まず、sys
とos
のimportを行います。
import sys, os
次にos.getcwd()
でカレントディレクトリのパスを確認してください。そこから先ほど落としてきたソースコードのディレクトリ内にある「ch03」へ移動します。Pythonでの移動はas.chdir('移動先のフォルダパス')
を使用します。
それでは、MNISTのデータセットDLを行ってみます。
# sys.path は importするファイルを検索するためのパスを示す文字列のリスト. # appendはリストの末尾に要素を足す. # os.pardirは親ディレクトリを表す. sys.path.append(os.pardir) # 親ディレクトリのファイルをimport. # from モジュール名 import クラス(又は関数)名 # dataset > mnist.py の load_mnistという関数を直接importして使用できるようにする. from dataset.mnist import load_mnist # 初回のみMNISTデータのDLを行うためネット接続が必須.少し待つ. # 2回目以降はローカルに保存したファイル(pickle)を読み込む # (訓練画像, 訓練ラベル), (テスト画像, テストラベル) = load_mnist(normalize=True, flatten=True, one_hot_label=False) # normalize:入力画像を0.0~1.0に正規化するかどうか.Falseならピクセルは元の0~255. # flatten:入力画像を平ら(1次元配列)にするかどうか. # Falseなら入力画像は1 * 28 * 28 の3次元配列. # Trueなら784個の要素を持つ1次元配列. # one_hot_label:ラベルをone-hot表現(後述)として格納するかどうか. (x_train, t_train), (x_test, t_test) = load_mnist(flatten=True, normalize=False)
One-hot表現とは?
正解となるラベルだけが1、それ以外は0となる配列のことです。
個人的にわかりやすいと思った解説が乗っているページは以下の通りです。
MNIST画像の表示
import numpy as np # 画像の表示にPIL(Python Image Library)モジュールを使用 from PIL import Image def img_show(img): # Numpyとして格納された画像データをPIL用のデータオブジェクトに変換 pil_img = Image.fromarray(np.uint8(img)) pil_img.show() img = x_train[0] label = t_train[0] # 表示する画像に対応したラベル内容 print(label) # 結果:5 # Numpy配列として1次元で格納されているため、 # 標準サイズの28 × 28 = 784という数値になっている. print(img.shape) # 結果:(784,) # 28×28の標準サイズに再変形 img = img.reshape(28, 28) img_show(img)
上記のような処理を行うことで、「5」を表す画像が表示されると思います。
ニューラルネットワークの推論処理
ネットワークは入力層が(28*28=)784個、出力層を10個(0~9までのクラス数)のニューロンで構成します。
又、隠れ層は2つ存在するとして、1つ目の隠れ層が50個、2つ目の層が100個といった任意のニューロン数で構成されるものとします。
import pickl # 中身は以前実装していたsigmoid, softmaxと同様 from common.functions import sigmoid, softmax def get_data(): # 前処理として正規化(normalize)を行う (x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, flatten=True, one_hot_label=False) return x_test, t_test def init_network(): # sample_weight.pklに保存された学習済みの重みとバイアスのパラメータを読み込む. # 重みとバイアスのパラメータはディクショナリ型の変数として保存されている. with open("sample_weight.pkl", 'rb') as f: network = pickle.load(f) return network # 各ラベルの確率をNumPy配列で出力. # [(0の確率が)0.1, (1の確率が)0.3, (2の確率が)0.2...] def predict(network, x): w1, w2, w3 = network['W1'], network['W2'], network['W3'] b1, b2, b3 = network['b1'], network['b2'], network['b3'] a1 = np,dot(x, w1) + b1 z1 = sigmoid(a1) a2 = np.dot(z1, w2) + b2 z2 = sigmoid(a2) a3 = np.dot(z2, w3) + b3 y = softmax(a3) return y x, t = get_data() network = init_network() # ニューラルネットワークが正解したカウント変数 accuracy_cnt = 0 for i in range(len(x)): y = predict(network, x[i]) # 最も確率の高い要素のインデックスを取得 p= np.argmax(y) # ニューラルネットワークが予測した答えと正解ラベルを比較 if p == t[i]: accuracy_cnt += 1 # 一致していれば正解数をインクリメント # 正解数 / xの要素数 = 正解率 print("Accuracy:" + str(float(accuracy_cnt) / len(x)) # 結果:0.9352 つまり93.52%
for文の中の、y = predict()
で各ラベルの確率をNumPy配列として出力し、p = np.argmax(y)
で最も(確率の)値が大きなインデックスを取得し、それを予測結果とします。今回、前処理として正規化を行っています。前処理には識別性能の向上や学習の高速化などに効果があります。前処理には例えば以下のようなものがあります。
- 「正規化」:学習データの値が0~1の範囲に収まるよう加工を施す。
- 方法:学習データの中の最大値を取り出し、全てのデータをその値で割る。
- 注意:多次元データでは正規化が有効であるかはわからない。例えば、各次元が(身長、体重、体脂肪率)だった場合は各成分毎に正規化が必要かもしれない。
- 「標準化」:入力データの平均を0に、分散を1にするような加工を施す。データは概ね-1~1の範囲に留まる。
- メリット:観測して集めたデータは必ずしも範囲が明確ではないため、統計的なパラメータを使用して標準化を行うのが一般的。
- 「白色化」:データ全体の分布の形状を均一にする。(一旦簡略化)
最後に
今回は学習済みのパラメーターを用いて分類問題を簡易的に扱って見ました。少し手元で実行するために手間取ったりもしましたが、無事ここまで読了することができました。次回はこれをバッチ処理にしていきたいと思います。またバッチ処理のメリットも合わせて記事にしていきます。次回で本の3章が終わりとなります。まだ先は長いですが、頑張って行きましょう。
参考
【読書メモ】Deep Learningを学ぶ〜分類問題で使用されるソフトマックス関数について〜Part 13
最初に
本記事は以下の本から得た知見を自分用のメモも兼ねてまとめた記事となります。ただし本の内容をそのまま書き写ししているわけではなく、ある程度自身で調べたものや感想が混じっています。ご了承ください。
ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装
また、本記事は株式会社よむネコのプロジェクトの業務内のお時間を頂いて蓄積した知見となります。
以下会社URLです。
今回は、主に分類問題で使用されるソフトマックス関数について学んでいきたいと思います。
ソフトマックス関数の式
このソフトマックス関数とは、複数ある出力の数値を合計した時に1.0
としてくれる関数です。つまり犬の写真を入力データとした時に、犬である確率が0.7
、猫である確率が0.3
と言ったように、確率として解釈できる値へ変換してくれます。
式の特徴としては以下のような点が挙げられます。
- 0 < < 1
これを踏まえた上で、ソフトマックス関数の式を表すと以下のようになります。
ここで、Σという数列の和自体に慣れていない人向けに式の解説しておきます。
まずはを表す指数関数です(eはネイピア数を表します)。
入力信号はとします。出力がです。
この式内の分母にあるは、が出力層の数で、は1番目の入力信号の値から順に足していくという意味です。この場合は1番目の出力からn番目までの入力信号の値()の総和となります。
のとは、番目の出力をさします。例えば、3個の出力の中から2番目の出力値を得たい場合には以下のようになります(ここで出力値の総和をとします)。
ソフトマックス関数の実装
先ほどの式をそのままPythonで実装すると以下のようになります。
def softmax_func(a): exp_a = np.exp(a) sum_exp_a = np.sum(exp_a) y = exp_a / sum_exp_a return y a = np.array([0.3, 2.9, 4.0]) softmax_func(a) array([0.01821127, 0.24519181, 0.73659691])
ただしこのままでは、入力値が大きいと指数関数の計算をした時にオーバーフロー(値が大きすぎて表示できなくなった状態)が発生する危険性があります。
対策としては端的にいうと、入力値の中の最大値を全ての入力値から引くのが一般的な手法とされています(を用いた式も本には書いてありましたが省きます)。この要素を先ほどの実装に加えると以下のようになります。
def softmax_func_v2(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 a = np.array([0.3, 2.9, 4.0]) softmax_func_v2(a) array([0.01821127, 0.24519181, 0.73659691]) y = softmax_func_v2(a) np.sum(y) 1.0
ソフトマックス関数と分類問題
先ほど、ソフトマックス関数を用いることで、確率として解釈できる値へ変換してくれる、と言いました。分類問題では、一般的には、出力値の中で一番大きい値になる入力信号を発したクラスを認識結果とします。つまり犬である確率が0.7
、猫である確率が0.3
となった場合は、認識結果は「犬」になります。
また、ソフトマックス関数を適用したとしても出力の一番大きいニューロンの場所は変わりません。そのため、分類問題を扱う際には、一般的に出力層の活性化関数であるソフトマックス関数を省略しています(指数関数の計算はそれなりにPCに負荷がかかるため)。では今回学んだソフトマックス関数がどこで重要になるかというと、ニューラルネットワークが問題を扱う際の「学習」フェーズとなります。
分類問題の出力層
出力層の数は扱う問題によって適宜決定する必要があります。分類問題では、先ほどの例で「犬」と「猫」を分ける場合、出力層は2つとなります。ここに「兎」や「魚」を加える場合は出力層は合計4つです。
最後に
今回はソフトマックス関数について学んでいきました。一見難しそうに見えますが、少し時間をかけて紐解いてみるとある程度理解することができました。次は実際に手書き数字の認識という実践問題に取り組んでいきます。
参考
【読書メモ】Deep Learningを学ぶ〜機械学習の問題設定について〜Part 12
最初に
本記事は以下の本から得た知見を自分用のメモも兼ねてまとめた記事となります。ただし本の内容をそのまま書き写ししているわけではなく、ある程度自身で調べたものや感想が混じっています。ご了承ください。
ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装
また、本記事は株式会社よむネコのプロジェクトの業務内のお時間を頂いて蓄積した知見となります。
以下会社URLです。
今回は、機械学習の問題設定を適切に行うにあたって、「機械学習が扱う問題の種類」について学んでいきたいと思います。
機械学習の問題について
機械学習を使用する場合に、問題設定を考えることはかなり重要です。問題を設定するのは人間にしかできず、できる人自体も限られてきます。
機械学習が扱う問題は大きく「分類問題」と「回帰問題」に分けることができます。
ニューラルネットワークはこの問題のどちらも扱うことができますが、どちらを扱うかで出力層の活性化関数を変更する必要があります。
回帰(Regression)問題とは
回帰とは、ある入力データから連続的な数値の予測を行う問題です。
データの特徴、傾向を見て、具体的な数字を予測する問題は回帰に分けられます。
例:
- 株価分析
- 過去の予算広告ごとの売上データをもとにして、広告予算の増加による商品の売上増加値を予測
- 人の写った画像からその人の体重を予測
一般的に回帰問題では恒等関数を使用します。
分類(Classify)問題とは
分類とは、データが属するクラスを予測する問題です。
回帰と違い、具体的な数字を予測するのではなく、入力データを与えられたクラスに分類(ラベリング)することを目的としています。
とくに、クラス数が2つのみの場合は、2値分類と呼ばれています。
2値分類の場合は出力が0
,1
になります。
それよりクラス数が多くなった場合には、多クラス分類(もしくはn値分類)と呼ばれています。
この場合の出力は0
,1
....n
となります。
例:
- 学生のプロフィールから、その学生が合格か不合格かを予測(2値分類)
- 学生の課題提出などの詳細な情報から、学生の評価を予測(多クラス分類)
- 顧客の購買情報から、その顧客が新商品を購入するか否かを予測(2値分類)
- 人の写った画像から、その人の性別を予測(2値分類)
一般的に分類問題ではソフトマックス関数を使用します。
二つの問題の見分け方
前述した通り、
- 回帰=予測
- 分類=ラベリング
となります。
出力はいずれも数値となります。出力が数値になるような問題であれば機械学習で扱うことができる可能性があり、また、分類問題なのか回帰問題なのかを見分けることが可能となります。
最後に
今回は機械学習で扱う問題設定について少し深掘りしてみました。恒等関数についてはこれまでの学習の中でも少し登場していましたが、新しくソフトマックス関数というのが登場してきました。次回はソフトマックス関数の実装について詳しく学んでいきたいと思います。
参考
【読書メモ】Deep Learningを学ぶ〜ニューラルネットワークの実装〜Part 11
最初に
本記事は以下の本から得た知見を自分用のメモも兼ねてまとめた記事となります。ただし本の内容をそのまま書き写ししているわけではなく、ある程度自身で調べたものや感想が混じっています。ご了承ください。
ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装
また、本記事は株式会社よむネコのプロジェクトの業務内のお時間を頂いて蓄積した知見となります。
以下会社URLです。
今回は、前回学んだNumPy行列を用いてニューラルネットワークの実装を行なっていきます。
単純なニューラルネットワークの実装
最初なので、バイアスと活性化関数を省略し、重みだけが存在する以下のような図のニューラルネットワークを実装します。
x = np.array([1, 2]) # w = array([[x1の信号の重み], [x2の信号の重み]]) w = np.array([[1, 3, 5], [2, 4, 6]]) y = np.dot(x, w) print(y) # 出力 #[ 5 11 17]
3層ニューラルネットワークの実装
以下の図に示す3層ニューラルネットワークの、入力から出力へ向けた処理(これをフォワード処理という)を実装していきます。
ここから、「入力層→第1層目の1番目のニューロンへ向かう信号」にだけ注目します。記号を用いた図が以下のものです。
(記号についての記事はこちら:【読書メモ】Deep Learningを学ぶ〜ニューラルネットワークで使用する記号〜Part 10.5)
(ちなみにバイアスニューロンは一つだけなため、右下のインデックスは一つだけです)
を式で表すと以下のようになります。
先ほどの図の中の第1層目に出てくる、, , は、行列の内積を用いてまとめて一つの式で表すことができます。
, は、行列の内積を用いてまとめて一つの式で表すことができます。
・( , .....) を とします。
・(, .....) を X とします。
・(, .....)を とします。
を とします。
これにまずは第1層のを求めるために仮値を入れ、NumPyを使用して実装した場合は以下のようになります。
X = np.array([1.0, 0.5]) W1 = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]]) B1 = np.array([0.1, 0.2, 0.3]) A1 = np.dot(X, W1) + B1 print(A1) # 出力 # [0.3 0.7 1.1]
次は活性化関数を用いて、を変換(変換後の値はとする)します。
ここでのはシグモイド関数を使用してみます。
def sigmoid_func(x): return 1 / (1 + np.exp(-x)) Z1 = sigmoid_func(A1) print(Z1) # 出力 # [0.57444252 0.66818777 0.75026011]
を求める際には先ほど導き出したを使用して、同様の処理を行うことで出力値が得られます。
ただ、出力層に向けた活性化関数だけは、入力をそのまま出力するものになります。
def identity_func(x): return x
このidentity_func(x)
のような関数を「恒等関数」と言います。
最後に
これでニューラルネットワークの実装に必要な基礎知識を少し得ることができました。今回はフォワード処理についてでしたが次はバックワード処理となります。少しずつでも頑張っていきます。
【読書メモ】Deep Learningを学ぶ〜ニューラルネットワークで使用する記号〜Part 10.5
最初に
本記事は以下の本から得た知見を自分用のメモも兼ねてまとめた記事となります。ただし本の内容をそのまま書き写ししているわけではなく、ある程度自身で調べたものや感想が混じっています。ご了承ください。
ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装
また、本記事は株式会社よむネコのプロジェクトの業務内のお時間を頂いて蓄積した知見となります。
以下会社URLです。
今回はニューラルネットワークでの処理を説明する場合に使用される記号について学んでいきます。
記号の確認
以下の図を確認してください。今回は例としてという記号を使用しています。
つまりは第0層の2番目のニューロンから、第1層の3番目のニューロンへの重みであることになります。
最後に
今回は記号紹介だけだったのでさらっと終わろうと思います。ここで学んだことは次回から本格的に使用していきますので、しっかり理解しておきましょう。
【読書メモ】Deep Learningを学ぶ〜NumPyを用いた行列の計算〜Part 10
最初に
本記事は以下の本から得た知見を自分用のメモも兼ねてまとめた記事となります。ただし本の内容をそのまま書き写ししているわけではなく、ある程度自身で調べたものや感想が混じっています。ご了承ください。
ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装
また、本記事は株式会社よむネコのプロジェクトの業務内のお時間を頂いて蓄積した知見となります。
以下会社URLです。
今回はNumPy
を用いた多次元配列に関する計算について学んでいきます。
多次元配列を計算する実装
1次元配列と次元数の取得
import numpy as np one_d_array = np.array([0, 1, 2, 3]) np.ndim(one_d_array) # 配列の次元数を取得 # 出力 : 1
2次元配列(行列)と要素数の取得
import numpy as np two_d_array = np.array([[0, 1], [2, 3], [4, 5]]) print(two_d_array) # 出力 #[[0 1] # [2 3] # [4 5]] np.ndim(two_d_array) # 出力 # 2 two_d_array.shape # 各次元の要素数の取得 # 出力 #(3, 2) two_d_array.shape[0] # 指定した次元の配列が持つ要素数 # 出力 # 3
2次元配列は行列(matrix)と呼ばれています。配列の横方向の並びを行(row)、縦方向の並びを列(column)と呼びます。Excelなどをよく使う人なら馴染みがあるかと思います。
行列の内積(ドット積)
行列の実際の内積を計算する方法は以下のスライドがとても参考になると思います。
予め、注意点として挙げておくと、1つ目の行列の1次元目と、2つ目の行列の0次元目の数は一致させる必要があります。
x1 = np.array([[1, 2, 3], [4, 5, 6]]) # 0次元目:2、1次元目:3 x2 = np.array([[1, 2], [3, 4], [5, 6]]) # 0次元目:3、2次元目2 y = np.dot(x1, x2) # 行列の積(ドット積) print(y) # 出力 # array([[22, 28], # [49, 64]]) y.shape # 出力 # (2, 2)
最後に
今回はNumPyでの行列計算方法についてでした。次回からは今回得た知識を用いて実際にニューラルネットワークの内積を実装していくこととなります。多分次回の記事は長くなりますが、お付き合い頂けると嬉しいです。