質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.84%

  • Python

    6339questions

    Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

ゼロから学ぶDeepLearning 4章についての質問

解決済

回答 2

投稿

  • 評価
  • クリップ 2
  • VIEW 340

ganariya2525

score 18

前提・実現したいこと

現在ゼロから学ぶDeepLearningを使用して機械学習について勉強しています。
その中で、第四章の二層のネットワーク作成で躓いてしまったことがありました。
作成自体は理解できたのですが、なぜうまく動作しているのか理解ができませんでした。

二層のネットワークのコード

import sys, os
sys.path.append(os.pardir)
from common.functions import *
from common.gradient import numerical_gradient

class TwoLayerNet:

    def __init__(self, input_size, hidden_size, output_size, weight_init_std = 0.01):
        """
            input_size・・入力層の数
            hidden_size・・中間層の数
            output_size・・出力層の数
            weight_init_std
        """

        #重みの初期化
        self.params = {}
        self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)
        self.params['b1'] = np.zeros(hidden_size)
        self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)
        self.params['b2'] = np.zeros(output_size)


    def predict(self, x):
        """
            入力値をもとに予測をする
        """

        W1, W2 = self.params['W1'], self.params['W2']
        b1, b2 = self.params['b1'], self.params['b2']

        a1 = np.dot(x, W1) + b1
        z1 = sigmoid(a1)
        a2 = np.dot(z1, W2) +  b2
        y = softmax(a2)
        return y


    def loss(self, x, t):
        """
            損失関数・・どれぐらい教師データと値が違うかを求める 返り値が大きいほど合っていない
            x・・入力データ
            t・・教師データ
        """
        y = self.predict(x)
        return cross_entropy_error(y, t)


    def accuracy(self, x, t):
        """
            返り値・・0から1の値 大きいほど入力データからの予測値と、教師データの値が一致している
        """
        y = self.predict(x)
        y = np.argmax(y, axis=1)
        t = np.argmax(t, axis=1)

        accuracy = np.sum(y == t) / float(x.shape[0])
        return accuracy


    def numerical_gradient(self, x, t):
        """
            重みパラメータに対する勾配を求める
            x・・入力データ
            t・・教師データ
            grads・・勾配を保持するディクショナリ変数
        """
        loss_W = lambda W: self.loss(x, t)   #loss_Wはloss関数と等しい
        grads = {}
        grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
        grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
        grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
        grads['b2'] = numerical_gradient(loss_W, self.params['b2'])
        return grads

上記の二層ネットワーククラス内で使用しているnumerical_gradientのコード(本に付属しているコード)

def numerical_gradient(f, x):
    h = 1e-4 # 0.0001
    grad = np.zeros_like(x)

    it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
    while not it.finished:
        idx = it.multi_index
        tmp_val = x[idx]
        x[idx] = float(tmp_val) + h
        fxh1 = f(x) # f(x+h)

        x[idx] = tmp_val - h 
        fxh2 = f(x) # f(x-h)
        grad[idx] = (fxh1 - fxh2) / (2*h)

        x[idx] = tmp_val # 値を元に戻す
        it.iternext()   

    return grad

上記2つのコードに対しての入力コード

net = TwoLayerNet(input_size=784, hidden_size=100, output_size=10)
x = np.random.rand(100, 784) #ダミーの入力データ 100枚
y = net.predict(x)
print(y.shape)   #(100,10)

t = np.random.rand(100, 10) #ダミーの正解ラベル(100枚分)
grads = net.numerical_gradient(x, t)

理解できない点

1.TwoLayerNetクラスのnumerical_gradient関数内で使用されているnumerical_gradient関数は,selfがついていないため、クラスの外側にあるcommon.numerical_gradient関数を使用しているという認識で正しいでしょうか?

2.TwoLayerNetクラスのnumerical_gradient関数を使用した勾配がどうして合っているのかわかりません。
numerical_gradient関数内のラムダ式「loss_W=lamda W:self.loss(x, t)」の引数(x,t)は、TwoLayerNetクラスのnumerical_gradient関数の引数である(self, x,t)の(x,t)から使用しているのでしょうか?

3.TwoLayerNetクラスのnumerical_gradient関数内で使用されている
「grads['W1'] = numerical_gradient(loss_W, self.params['W1'])」についてわからないことがあります。
common.numerical_gradient関数では引数に(f,x)(f・・関数,x・・重み'W1'など)を取っています。
common.numerical_gradient関数の「fxh1 = f(x)」で、self.params['W1']をf関数に入れていますが、このf関数は ラムダ式である「lamda W: self.loss(x, t)」であるため、self.params['W1']と引数に渡しても、self.params['W2']を引数に渡しても結果が変わらないだろうと思いました。
しかし、色々とデータを代入してみると、['W1']の場合と['W2']の場合で結果が異なってしまいました。どうして違う結果になるのでしょうか。

よろしくお願いいたします。

補足情報(言語/FW/ツール等のバージョンなど)

使用環境:Mac OS High Sierra
使用ツール:Jupyter Notebook

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

+2

  1. はいそうです。プログラムの先頭部分にfrom common.gradient import numerical_gradient とあるのでcommon以下のgradient.pyに定義されているnumerical_gradientを使用しています。

  2. はいそうです。TwoLayerNetクラスのnumerical_gradient関数の引数(x, t)がラムダ式に渡され、loss_Wに格納されています。

  3. まずself.params['W1']self.params['W2']は初期値が異なります。以下のコードで確認してみました。

import numpy as np
from pprint import pprint

input_size=784
hidden_size = 100
output_size=10
weight_init_std = 0.01
W1 = weight_init_std * np.random.randn(input_size, hidden_size)
pprint(W1)

"""
array([[ -4.66963760e-03,  -4.61920583e-03,  -1.34628863e-02, ...,
         -7.12342008e-03,  -9.51390931e-03,  -1.42363695e-02],
       [  5.15248205e-03,  -1.81531005e-02,  -9.33031556e-03, ...,
         -6.03827853e-03,   2.32682820e-02,   1.14371378e-02],
       [ -1.47480497e-03,  -1.16778968e-02,  -5.36428583e-03, ...,
          3.24273403e-03,   2.58535969e-03,   4.07705684e-03],
       ..., 
       [ -6.12870871e-04,   2.27194176e-02,  -9.20775174e-03, ...,
         -6.56353954e-03,  -4.82236650e-03,  -8.97790691e-03],
       [ -1.38384077e-02,  -1.03076298e-02,   3.97350335e-03, ...,
         -4.14367214e-03,  -7.06703864e-06,   9.48678489e-03],
       [ -1.24808408e-03,  -4.70239299e-03,   5.48649149e-03, ...,
          1.01967985e-02,   1.20364877e-02,   2.37953073e-03]])
"""

W2 = weight_init_std * np.random.randn(input_size, hidden_size)
pprint(W2)

""" 
array([[-0.00076081, -0.00396194,  0.00877313, ..., -0.01413236,
         0.00682878,  0.00223497],
       [ 0.00896433,  0.00138473, -0.00574664, ...,  0.00085467,
         0.01578464,  0.00760901],
       [ 0.00133846, -0.012744  ,  0.00191541, ..., -0.01548384,
        -0.01866372,  0.01076827],
       ..., 
       [ 0.00888638,  0.00104892,  0.01148437, ...,  0.00610676,
        -0.00137109,  0.00020582],
       [ 0.00232449, -0.00431334, -0.0065821 , ...,  0.02289721,
         0.00331537, -0.0168427 ],
       [-0.01369978,  0.00572917,  0.00457355, ...,  0.01380905,
         0.00524373, -0.00342188]])
"""

したがってloss_Wは引数共通であっても両者値が異なるので結果が異なるということになります。

ごめんなさい、最後のこの文の意味がよくわかりませんでした。

しかし、色々とデータを代入してみると、['W1']の場合と['W2']の場合で結果が異なってしまいました。どうして違う結果になるのでしょうか。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/01/14 13:42

    1,2の回答ありがとうございます

    3についてなのですが
    クラス外で定義されているnumerical_gradient関数では、引数である「f」を使用して、微分を行っています。
    この時、例えばnumerical_gradient関数の引数である(f,x)のxに入っているself.params['W1']の値を使用して
    fxh1 = f(x)
    となっていますが、f関数はもとを辿ればTwoLayerNetクラスのloss関数で(x,t)を使用するため、結局調整したい重みであるself.params['W1']を使用していないように感じました。

    キャンセル

  • 2018/01/17 22:11

    すみません自分も理解不足で回答を書くのにだいぶ時間がかかってしまいました、説明にコードを書くこともあり上記の回答とは別の回答として投稿させていただきます。

    キャンセル

checkベストアンサー

+1

この時、例えばnumerical_gradient関数の引数である(f,x)のxに入っているself.params['W1']の値を使用して
fxh1 = f(x) 
となっていますが、f関数はもとを辿ればTwoLayerNetクラスのloss関数で(x,t)を使用するため、結局調整したい重みであるself.params['W1']を使用していないように感じました。

numerical_gradient関数の引数である(f,x)のxはself.params["W1"]の参照を渡しています。numerical_gradient関数で要素毎にx[idx] = float(tmp_val) + hx[idx] = tmp_val - hでxを更新しています。このxはself.params["W1"]の参照であるためf(x)が実行されるたびに更新されるという流れになります。
さすがに言葉のみでの説明ではわかりにくいので簡単にコードをまとめました。

# ゼロから学ぶニューラルネットワーク4章の(4.4.2)ニューラルネットワークに対する勾配で使われているコードを使います
import numpy as np
from pprint import pprint


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


def cross_entropy_error(y, t):
    if y.ndim == 1:
        t = t.reshape(1, t.size)
        y = y.reshape(1, y.size)

    # 教師データがone-hot-vectorの場合、正解ラベルのインデックスに変換
    if t.size == y.size:
        t = t.argmax(axis=1)

    batch_size = y.shape[0]
    return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size


def numerical_gradient(f, x):
    h = 1e-4  # 0.0001
    grad = np.zeros_like(x)

    it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
    while not it.finished:
        idx = it.multi_index
        tmp_val = x[idx]
        x[idx] = float(tmp_val) + h
        pprint(idx)
        # hが0.0001のため小数点四桁目以下を切り捨てています
        pprint("init: {}".format(round(tmp_val, 4)))
        pprint("f(x+h): {}".format(round(x[idx], 4)))
        fxh1 = f(x)  # f(x+h)
        x[idx] = tmp_val - h
        pprint("f(x-h): {}".format(round(x[idx], 4)))
        fxh2 = f(x)  # f(x-h)
        grad[idx] = (fxh1 - fxh2) / (2 * h)
        x[idx] = tmp_val  # 値を元に戻す
        it.iternext()
        break # 1ループ目で終わらせます
    return grad


class simpleNet:
    def __init__(self):
        self.W = np.random.randn(2, 3)

    def predict(self, x):
            # 参照確認用のコメントになります
        pprint("predict: {}".format(round(self.W[0][0], 4)))
        return np.dot(x, self.W)

    def loss(self, x, t):
        z = self.predict(x)
        y = softmax(z)
        loss = cross_entropy_error(y, t)
        return loss

def main():
    x = np.array([0.6, 0.9])
    t = np.array([0, 0, 1])

    net = simpleNet()
    f = lambda w: net.loss(x, t)
    dW = numerical_gradient(f, net.W)


if __name__ == '__main__':
    main()
    """
    (0, 0)
    まずself.Wの初期値を確認します
    今回はself.Wの中身全ては確認しません
    'init: -1.2436'
    'f(x+h): -1.2435'
    参照が更新されているので上と下の値が同じであることがわかります
    'predict: -1.2435'
    'f(x-h): -1.2437'
    上記と同様です
    'predict: -1.2437'
    """

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.84%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • Python

    6339questions

    Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。