🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Keras

Kerasは、TheanoやTensorFlow/CNTK対応のラッパーライブラリです。DeepLearningの数学的部分を短いコードでネットワークとして表現することが可能。DeepLearningの最新手法を迅速に試すことができます。

強化学習

強化学習とは、ある環境下のエージェントが現状を推測し行動を決定することで報酬を獲得するという見解から、その報酬を最大限に得る方策を学ぶ機械学習のことを指します。問題解決時に得る報酬が選択結果によって変化することで、より良い行動を選択しようと学習する点が特徴です。

Python

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

Q&A

解決済

4回答

1443閲覧

明示的に呼び出してない関数が呼び出される仕組み?

koyamashinji

総合スコア45

Keras

Kerasは、TheanoやTensorFlow/CNTK対応のラッパーライブラリです。DeepLearningの数学的部分を短いコードでネットワークとして表現することが可能。DeepLearningの最新手法を迅速に試すことができます。

強化学習

強化学習とは、ある環境下のエージェントが現状を推測し行動を決定することで報酬を獲得するという見解から、その報酬を最大限に得る方策を学ぶ機械学習のことを指します。問題解決時に得る報酬が選択結果によって変化することで、より良い行動を選択しようと学習する点が特徴です。

Python

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

0グッド

2クリップ

投稿2021/02/27 06:05

編集2021/02/27 06:13

ここのGithubレポジトリを見ながら、tensorflowKerasを使用し、強化学習を勉強しております。

わからないこと

以下、一部コードを抜粋しておりますが
(1)のtf.squeeze(self.critic(states, actions), 1)から、CriticNetworkクラスの関数(3)が呼び出されます。
どのような仕組みで呼び出されているのか
教えていただけますと大変助かります。

Agentクラスのインスタンス化

.learnで(1)が呼び出される

(2)self.critic = CriticNetwork(n_actions=n_actions, name='critic')で、CriticNetworkクラスがインスタンス化される
まではわかるのですが、そもそも、どこからもcallという名前の関数を呼び出してないのです。
どのような仕組みなのでしょうか。


networks.py

import os import tensorflow as tf import tensorflow.keras as keras from tensorflow.keras.layers import Dense from tensorflow.keras.optimizers import Adam ######## 省略 ######## class CriticNetwork(keras.Model): def __init__(self, fc1_dims=512, fc2_dims=512, n_actions=2, name='critic', chkpt_dir='tmp/ddpg'): super(CriticNetwork, self).__init__() self.fc1_dims = fc1_dims self.fc2_dims = fc2_dims self.model_name = name self.checkpoint_dir = chkpt_dir self.checkpoint_file = os.path.join(self.checkpoint_dir, self.model_name+'_ddpg.h5') self.fc1 = Dense(self.fc1_dims, activation='relu') self.fc2 = Dense(self.fc2_dims, activation='relu') self.q = Dense(1, activation=None) ###### (3) ####### def call(self, state, action): action_value = self.fc1(tf.concat([state, action], axis=1)) action_value = self.fc2(action_value) q = self.q(action_value) return q

ddpg_tf2.py

class Agent: def __init__(self, input_dims, alpha=0.001, beta=0.002, env=None, gamma=0.99, n_actions=2, max_size=1_000_000, tau=0.005, fc1=400, fc2=300, batch_size=64, noise=0.1): self.gamma = gamma self.tau = tau self.memory = ReplayBuffer(max_size, input_dims, n_actions) self.batch_size = batch_size self.n_actions = n_actions self.noise = noise self.max_action = env.action_space.high[0] self.min_action = env.action_space.low[0] # 各NNのインスタンス化 self.actor = ActorNetwork(n_actions=n_actions, name='actor')     ######## (2) ######### self.critic = CriticNetwork(n_actions=n_actions, name='critic')     ####################### self.target_actor = ActorNetwork(n_actions=n_actions, name='target_actor') self.target_critic = CriticNetwork(n_actions=n_actions, name='target_critic') # 各NNをcompile self.actor.compile(optimizer=Adam(learning_rate=alpha)) self.critic.compile(optimizer=Adam(learning_rate=beta)) self.target_actor.compile(optimizer=Adam(learning_rate=alpha)) self.target_critic.compile(optimizer=Adam(learning_rate=beta)) self.update_network_parameters(tau=1) # def learn(self): if self.memory.mem_cntr < self.batch_size: return state, action, reward, new_state, done = self.memory.sample_buffer(self.batch_size) states = tf.convert_to_tensor(state, dtype=tf.float32) states_ = tf.convert_to_tensor(new_state, dtype=tf.float32) rewards = tf.convert_to_tensor(reward, dtype=tf.float32) actions = tf.convert_to_tensor(action, dtype=tf.float32) # CRITIC LOSSの算出 with tf.GradientTape() as tape: target_actions = self.target_actor(states_) critic_value_ = tf.squeeze(self.target_critic(states_, target_actions), 1)        ######## (1) ######### critic_value = tf.squeeze(self.critic(states, actions), 1) ###################### target = reward + self.gamma*critic_value_*(1-done) critic_loss = keras.losses.MSE(target, critic_value) critic_network_gradient = tape.gradient(critic_loss, self.critic.trainable_variables) self.critic.optimizer.apply_gradients(zip(critic_network_gradient, self.critic.trainable_variables)) # ACTOR LOSSの算出 with tf.GradientTape() as tape: new_policy_actions = self.actor(states) actor_loss = -self.critic(states, new_policy_actions) actor_loss = tf.math.reduce_mean(actor_loss) actor_network_gradient = tape.gradient(actor_loss, self.actor.trainable_variables) self.actor.optimizer.apply_gradients(zip(actor_network_gradient, self.actor.trainable_variables))

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

ベストアンサー

少し長くなります。

まず、御存じのようにCriticNetworkは、keras.Model クラスを継承しています。
(call()は、このkeras.Model を継承して上書きされるべきものとして用意されています)

そして、keras.Modelクラス は、base_layer.Layer クラスを継承しています

base_layer.Layer クラスの中では
__call__()関数が定義されています。

閑話休題。pythonで__call__()関数は、下記のような使い方をされます。

例えば、

class MyClass: def __init__(self, val): self.value = val def get_value(self): return self.value def __call__(self, addition): print("__call__() is called.") self.value += addition

としましょう。そして次のように使ってみます。

>>> myc = MyClass(1) >>> print(myc.get_value()) 1 # インスタンスを引数付きで呼び出す >>> myc(addition=100) __call__() is called. >>> print(myc.get_value()) 101

このように、インスタンスmyc()を呼び出すことで、__call__()を呼べることが分かります。


 

ここでkerasのソースに戻りましょう。

実はcall関数は、このbase_layer.Layerクラス の __call__()関数の中で呼ばれています。
【ここ】
call_fn = self.call
として、self.callがcall_fnという名前に束縛され

【ここ】
outputs = call_fn(cast_inputs, *args, **kwargs)
というように、呼び出されます。
(実際には条件分岐でリンクした行が実行されない場合もあるようですが、別の部分でもcall_fn経由でcallが呼び出されているようです)

上のself.call は、keras.Model上はダミー扱いですが、keras.Modelを継承したクラスで上書きされることが想定されています。


 

お疲れ様でした。ここまでの流れを振り返りましょう。

(※main_ddpg.pyがスタート地点です。

main_ddpg.pyが、Agentクラスをインスタンス化[src]し、結果、ddpg_tf2.py中のAgent#__init__()が呼び出されています。

 ↓
Agent#__init__()の中で、CriticNetworkがインスタンス化されます[質問文の(2)]

main_ddpg.py の中でAgent#learn()を呼び出しています。[src]。Agent#learn()の中で、ddpg_tf2.pyの質問文にある(1)が呼び出されます。
「 critic_value = tf.squeeze(self.critic(states, actions), 1)」

このとき、self.critic(states, actions), 1)という呼び出し方をしていることから、ここでCriticNetworkの__call__()を呼びだすことになります。

この呼び出しが継承元に伝搬し、結局、base_layer.Layerクラス の __call__()関数が呼ばれることになります。

そして、__call__()関数内の処理で、call_fnという変数を通じて、keras.Modelを継承したCriticNetworkクラスのcall()が呼ばれることになります。

投稿2021/02/27 07:50

編集2021/02/27 07:56
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

koyamashinji

2021/02/27 10:01

qnoir様、 大変丁寧なご回答頂き感激しております。誠に、有難うございます。下名そもそも__call__の概念が分かっておりませんでしたので, かなり明確に理解できました。 少し疑問がわきましたが、 self.callがダミーであるというのは、どこから判断できるのでしょうか。 また「親クラスから 子クラスで定義したメソッドを呼び出す」という今回の様な方法は 少々 歪に感じますが、どういった状況において メリットがあるのでしょうか。もし宜しければお教えください。
退会済みユーザー

退会済みユーザー

2021/02/27 10:48 編集

>self.callが(keras.Model上は)ダミーであるというのは、どこから判断できるのでしょうか https://github.com/keras-team/keras/blob/0b89570b1601f25ebb959638aa7254b9394f7694/keras/engine/training.py#L460 ここに「raise NotImplementedError('When subclassing the `Model` class, you should ' 'implement a `call` method.')」(Modelをサブクラス化したときは、callメソッドを実装すること)と書かれており、call()がサブクラスで実装されておらずこの部分が呼び出された場合、NotImplementedErrorが発生することになります。 >「親クラスから 子クラスで定義したメソッドを呼び出す」という今回の様な方法は 少々 歪に感じますが、どういった状況において メリットがあるのでしょうか。 「子に楽をさせる」こと、および、「子にしてほしくないことをさせない」というメリットがあります。 「子に楽をさせる」とはすなわち、親クラスであらかじめプログラムに必要な前提要素を組み立てておき(__call__()内でcall()を呼んでいる前後のコードがそれに該当します)、本当に拡張すべき部分だけを開放する(=call()を子クラスで実装させる)という設計により、 子クラス側で前準備や後処理を書く必要がなくなり、子クラスでは本当にやりたいことに専念できるということです。 「子にしてほしくないことをさせない」とはすなわち、親クラスの中で精密に調整されたプログラムを、子が誤ってもしくは故意に壊すことをさせないということです。 壊されたくない部分は親側で処理し、拡張してよい部分をcall()として公開することによって、プログラム全体が安全に保たれます。 https://tf.wiki/en/basic/models.html の「Why is the model class override call() instead of __call__()?」と書かれている部分もよければお読みください。
koyamashinji

2021/02/27 12:59

なるほど、勉強になります。ご回答誠に有難うございました。一番丁寧にご説明頂いた貴殿をベストアンサーとさせていただきます。
guest

0

https://keras.io/api/layers/base_layer/#layer-class

のcallメソッドの説明に

call(self, *args, **kwargs): Called in __call__ after making sure build() has been called.

かんたん訳

build() が呼び出されたことを確認した後、__call__ の中で呼び出されます。

とクラスのドキュメントに明記されています。
説明を信じるなら __call__の中にcallを呼び出す実装があるはず、と考えるのが妥当です。

(以下編集して改変)

ではその__call__が何でいつ呼び出されるか? はすでに回答が付いている通り、比較的簡単にたどり着けるはずです。公式ドキュメントを__call__で検索してもいいでしょう。
ちなみにPython公式ドキュメントだとここです。
https://docs.python.org/ja/3/reference/datamodel.html#object.call

kerasとPythonの公式ドキュメントから

self.critic(states, actions)は実体として__call__を呼び出します。
callはその__call__ の中で呼び出されます。

という構造になっていることがわかるかと思います。


(追記)念のためModelがLayerのサブクラスか確認しておきます。

>>> from tensorflow.keras.models import Model >>> from tensorflow.keras.layers import Layer >>> issubclass(Model, Layer) True

投稿2021/02/27 07:11

編集2021/02/27 07:58
quickquip

総合スコア11231

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

toast-uz

2021/02/27 07:33 編集

なるほど。 サブクラスの関係はhttps://www.tensorflow.org/api_docs/python/tf/keras/Modelに記載されていますね。(1) __call__の特殊メソッドの性質、(2)サブクラスの関係、(3)Layerクラスでcallメソッドが__call__から内部的に呼ばれていること、の(1)(2)(3)をあわせることで、回答になると思います。(1)はy_waiwai様、(2)(3)はquickquip様の回答ですね。 (私のレベルでは、理解できないほど自明なこととは思いません。質問者様は文脈的に(1)もまだ勉強中の段階に思います)
quickquip

2021/02/27 07:59

確かにその通りだな、と思いましたので回答を改めました。ありがとうございます。
koyamashinji

2021/02/27 10:11

quickquip様、toat-uz様、 toast-uz様の仰るとおり下名はそもそも__call__メソッドについて理解が及んでおりませんでした。 素人質問でしたが ご回答及びコメント頂きまして深謝いたします。 しっかりと公式ドキュメントも読みながら、今後も学習を進めていこうと思います。 取り急ぎお礼いたします。
guest

0

こういう場合の調べ方について説明します。

text

1critic_value = tf.squeeze(self.critic(states, actions), 1) 2のように、self.criticというインスタンスが()を使って呼びだされるためには、__call__というメソッドがなければなりません。オブジェクトのあとに()があると、インタプリタが__call__というメソッドを探してそれを実行し、なければエラーをあげるからです。 3self.criticのクラスであるCriticNetworkでは__call__を定義していないので、そのbaseclassから継承しているはずです。

keras.Modelのbaseclassを調べて見ましょう。

python

1>>> 2>>> print(keras.Model.__bases__) 3(<class 'tensorflow.python.keras.engine.base_layer.Layer'>, <class 'tensorflow.python.keras.utils.version_utils.ModelVersionSelector'>) 4>>> print(keras.Model.__bases__[0].call) 5<function Layer.call at 0x0000027D761351E0> 6>>> print(keras.Model.__bases__[1].call) 7Traceback (most recent call last): 8 File "<stdin>", line 1, in <module> 9AttributeError: type object 'ModelVersionSelector' has no attribute 'call' 10>>> print(keras.Model.__bases__[0].__bases__) 11(<class 'tensorflow.python.module.module.Module'>, <class 'tensorflow.python.keras.utils.version_utils.LayerVersionSelector'>) 12>>> print(keras.Model.__bases__[0].__bases__[0].call) 13Traceback (most recent call last): 14 File "<stdin>", line 1, in <module> 15AttributeError: type object 'Module' has no attribute 'call' 16>>> print(keras.Model.__bases__[0].__bases__[1].call) 17Traceback (most recent call last): 18 File "<stdin>", line 1, in <module> 19AttributeError: type object 'LayerVersionSelector' has no attribute 'call'

これで、keras.Modelのcallは<class 'tensorflow.python.keras.engine.base_layer.Layer'>で定義されて継承していることがわかります。
そのソースを見るために、どこにソースがあるか調べて見ましょう。

python

1>>> print(keras.Model.__bases__[0].__module__) 2tensorflow.python.keras.engine.base_layer 3>>> 4>>> import sys 5>>> print(sys.modules[keras.Model.__bases__[0].__module__]) 6<module 'tensorflow.python.keras.engine.base_layer' from 'C:\Users\XXXXX\anaconda3\envs\py37\lib\site-packages\tensorflow\python\keras\engine\base_layer.py'>

では、このbase_layer.pyを見てみます。
クラスlayerの定義は、私のところのバージョンでは113行目から始まります。

python

1@keras_export('keras.layers.Layer') 2class Layer(module.Module, version_utils.LayerVersionSelector): 3 """This is the class from which all layers inherit.

169行目にコメントがあります。

python

1 * `call(self, *args, **kwargs)`: Called in `__call__` after making sure 2 `build()` has been called. `call()` performs the logic of applying the 3 layer to the input tensors (which should be passed in as argument).

469行目にcallの定義があります

python

1 @doc_controls.for_subclass_implementers 2 def call(self, inputs, **kwargs): # pylint: disable=unused-argument 3 """This is where the layer's logic lives.

901行目に__call__の定義があります。

python

1 def __call__(self, *args, **kwargs): 2 """Wraps `call`, applying pre- and post-processing steps.

999行目あたりでcallを使います。

python

1 if eager: 2 call_fn = self.call 3 name_scope = self._name 4 else: 5 name_scope = self._name_scope() # Avoid autoincrementing. 6 call_fn = self._autographed_call() 7

このとき呼び出されるcallはLayerクラスのcallではなく、selfつまりインスタンスの持つcallメソッドです。
1010行目あたりでcall_fnを使います。

python

1 with autocast_variable.enable_auto_cast_variables( 2 self._compute_dtype_object): 3 outputs = call_fn(inputs, *args, **kwargs)

critic_value = tf.squeeze(self.critic(states, actions), 1)が実行されると、以上の仕組みによりself.criticのcallが呼び出せされます。

投稿2021/02/27 08:28

ppaul

総合スコア24670

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

koyamashinji

2021/02/27 10:06

ppaul様、 この様な状況の調べ方について, 大変参考になるご回答を有難うございます。自身でも、さらに調べてみようと思います。大変有難うございました。
guest

0

python call、でぐぐると解説が出てきます

投稿2021/02/27 06:38

y_waiwai

総合スコア88038

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

toast-uz

2021/02/27 06:50 編集

__call__じゃないので、ちょっと事情は複雑です tf.keras.Modelのソースを追う必要があります
y_waiwai

2021/02/27 06:51

__call__だったら問題はないんでしょうか
toast-uz

2021/02/27 07:05 編集

__call__だったらpythonの特殊メソッドなので、 self.critic(states, actions) すなわちCriticNetwork(n_actions=n_actions, name='critic')(states, actions)という構文によってCriticNetworkクラスから作られたself.criticオブジェクトの__call__メソッドが呼び出されることは自明です。 しかし、今回はcallなので、tf.keras.Modelのcallをオーバーライドしており、ソースを追うとtf.keras.Modelのbuildから呼び出されており、じゃあbuildはどこから呼ばれているのか・・・と、呼び出し元とのつながりをどんどんさかのぼって調べる必要があり、複雑です。 __cal__とcallで同じ名前にしているので、おそらく__call__のように使うことを想定されたメソッドであるという推測は成り立ちますが、ソースを追わないと確証がとれないです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問