チラ裏備忘録

情報整理

Tensorflow 2.Xでの線形回帰

今回はTensorflow2で線形回帰を行ってみようと思います.

Linear Regression using TensorFlow 2.0 | by Dhiraj K | Heartbeat
のページを参考にTensorflow 2を用いた線形回帰のメモを残そうと思います.
※一部改変しています

インポート

import tensorflow as tf
import numpy as np

回帰用データ作成

x_train = np.linspace(-2, 2, 400)
y_train = 3*x_train**2 + 5*x_train + 3 + np.random.randn(400)

xを400点,yは適当な二次関数にノイズを加えたものを採用しました.
この二次の係数3,一次の係数5,定数項3をモデルが学習し,"それっぽい"線を引くことが目的です.
今回は面倒なのでテストデータは用意していません.
f:id:spookyboogie:20200521144729p:plain

モデル定義とインスタンス

class MyModel(tf.keras.Model):
  def __init__(self):
    super(MyModel, self).__init__()

    self.a1 = tf.Variable(1.0)
    self.a2 = tf.Variable(1.0)
    self.b = tf.Variable(1.0)
    self.params = [self.a1, self.a2, self.b]

  def call(self, x):
    return self.a1*x**2 + self.a2*x + self.b

model = MyModel()

2系から学び始めたため,VariableはPlaceholder等と共に既に廃止されたものと勝手に思っていましたが,普通に使えるのですね.
複数の層を用いたMNISTの分類では,call関数でフィードフォワードの繋がりを決定し出力である確率を返しましたが,今回の出力は単純に係数を掛けた予測値です.
参考記事のおかげで,call関数は入力から出力のプロセスを記述し出力を返す関数であると再認識できました.

損失関数と最適化手法の定義

loss_object = tf.keras.losses.MeanSquaredError()
optimizer = tf.keras.optimizers.SGD(learning_rate=0.01)

参考記事では,以下のようなloss関数を自ら定義しています.

def loss(y, pred):
    return tf.reduce_mean(tf.square(y - pred))

ただ,これってMeanSquaredErrorと同義じゃね?と思ったのでtf.keras.losses.MeanSquaredError()を用いました.

オプティマイザに関しても,参考記事では,

lr_weight, lr_bias = t.gradient(current_loss, [linear_model.Weight, linear_model.Bias])
linear_model.Weight.assign_sub(lr * lr_weight)
linear_model.Bias.assign_sub(lr * lr_bias)

のように,lossとモデルの各変数をgradientに与えて勾配を求め,assign_subというメソッドを使って,勾配と学習係数に掛け合わせた値で各変数を更新しているみたいですが,おそらくSGDと同じ原理ですので,今回はtf.keras.optimizers.SGD(learning_rate=0.01)としました.

このassign_subについて調べて見ると1系の関数で2系には非対応っぽいんですがどうなんでしょう…

学習用関数定義

@tf.function
def train_step(x, y):
  with tf.GradientTape() as tape:
    predictions = model(x)
    loss = loss_object(y, predictions)
  
  gradients = tape.gradient(loss, model.params)
  optimizer.apply_gradients(zip(gradients, model.trainable_variables))
  1. 予測値(モデルの出力)を求める
  2. 正解と予測値からlossを求める
  3. lossとモデルの各変数から勾配を求める
  4. 勾配を用いてSGDで重みを更新

model.paramsはモデルの各変数をリスト形式にまとめたものです.

学習

EPOCH = 100

for epoch in range(EPOCH):
  # 学習
  train_step(x_train, y_train)

  # 正解と予測値からlossを求める(出力用)
  real_loss = loss_object(y_train, model(x_train))

  # 出力
  template = "Epoch {}, Loss {}"
  print(template.format(epoch+1, real_loss.numpy()))

特筆すべきことはありません.

結果

Epoch 1, Loss 45.20094680786133
Epoch 2, Loss 40.685359954833984
Epoch 3, Loss 36.73365020751953
Epoch 4, Loss 33.267723083496094
Epoch 5, Loss 30.220775604248047
Epoch 6, Loss 27.53564453125
Epoch 7, Loss 25.163389205932617
Epoch 8, Loss 23.062070846557617
Epoch 9, Loss 21.195751190185547
Epoch 10, Loss 19.53359603881836
Epoch 11, Loss 18.04913330078125
Epoch 12, Loss 16.71961212158203
Epoch 13, Loss 15.525479316711426
Epoch 14, Loss 14.4498929977417
Epoch 15, Loss 13.478344917297363
Epoch 16, Loss 12.598313331604004
Epoch 17, Loss 11.798981666564941
Epoch 18, Loss 11.070996284484863
Epoch 19, Loss 10.40625286102295
Epoch 20, Loss 9.797710418701172
Epoch 21, Loss 9.239261627197266
Epoch 22, Loss 8.725581169128418
Epoch 23, Loss 8.252019882202148
Epoch 24, Loss 7.814520835876465
Epoch 25, Loss 7.409524917602539
Epoch 26, Loss 7.033907413482666
Epoch 27, Loss 6.684916973114014
Epoch 28, Loss 6.3601250648498535
Epoch 29, Loss 6.0573859214782715
Epoch 30, Loss 5.774795055389404
Epoch 31, Loss 5.510659217834473
Epoch 32, Loss 5.263465404510498
Epoch 33, Loss 5.031863212585449
Epoch 34, Loss 4.814640045166016
Epoch 35, Loss 4.61070442199707
Epoch 36, Loss 4.419074535369873
Epoch 37, Loss 4.238860130310059
Epoch 38, Loss 4.0692548751831055
Epoch 39, Loss 3.9095232486724854
Epoch 40, Loss 3.758997917175293
…(中略)…
Epoch 90, Loss 1.2808070182800293
Epoch 91, Loss 1.2708830833435059
Epoch 92, Loss 1.2614418268203735
Epoch 93, Loss 1.252458095550537
Epoch 94, Loss 1.2439079284667969
Epoch 95, Loss 1.235769271850586
Epoch 96, Loss 1.2280209064483643
Epoch 97, Loss 1.2206425666809082
Epoch 98, Loss 1.2136154174804688
Epoch 99, Loss 1.2069215774536133
Epoch 100, Loss 1.2005436420440674

参考記事では一次関数の近似でlossが20エポックほどで1を下回っているんですが,今回は二次関数を利用したからなのか,はたまた改変した部分が影響を及ぼしたのかわかりませんが,100エポックでlossは1程度という結果になりました.

各係数を表示

# モデルの重みをリスト形式で取得
model.get_weights() # [3.1935437, 4.79401, 2.541419]
# また,以下のように直接モデルの変数を参照してもよいです
model.a1.numpy() # 3.1935437
model.a2.numpy() # 4.79401
model.b.numpy() # 2.541419

理想はそれぞれa1=3,a2=5,b=3ですので,バイアス項のノイズを考慮すればこんなものなのかもしれません(適当).

最後に,得られた係数で描画した曲線を,元のグラフと重ねて貼っておきます.
f:id:spookyboogie:20200521151136p:plain
個人的に,各係数の値だけを理想値と比較するとズレが大きいように感じていましたが,グラフにプロットしてみるとかなり忠実に元グラフをなぞれていますね!
線形回帰の場合,必ずしも各係数を完璧なレベルで近似する必要はないのかもしれません.

matplotlib 複数グラフのプロット【plt.subplots/fig.add_subplot】

plt.subplots

大まかな流れは,plt.subplotsを使って表示したいグラフの数だけaxを取得し,ax.plotでプロットするという感じです.

import numpy as np
import matplotlib.pyplot as plt

# 0〜10を100分割
x = np.linspace(0, 10, 100)

# Figureインスタンス及びAxesインスタンスの生成
fig, (ax1, ax2) = plt.subplots(1, 2, sharey=True, figsize=(10,4))

#各領域にグラフをプロットする
ax1.plot(x, np.sin(x), label="sin(x)")
ax2.plot(x, np.cos(x), label="cos(x)")

# グラフ上にタイトルを付ける
ax1.set_title("sin(x)")
ax2.set_title("cos(x)")

# 表示
plt.show()

f:id:spookyboogie:20200519233522p:plain

plt.subplotsでは,引数に行数と列数を与えることで,グラフの配置(レイアウト)を指定できます.ここでは1行2列としました.

また,sharey=Trueとすることで,同じ行に存在するグラフで縦軸を共有することができます.
sharex=Trueとすると,同じ列間で横軸を共有します.

さらに,figsize=(横, 縦)でフィギュア全体のサイズ(≠各グラフのサイズ)を指定できます.数字はインチ指定で,デフォルトは(8, 6)です.

ax.set_title(タイトル名)を指定することで,グラフの上部に任意のタイトルをつけることができます.

2行3列の例

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0, 10, 100)

fig, ((ax01, ax02, ax03), (ax11, ax12, ax13)) = plt.subplots(2, 3, sharey=True, sharex=True, figsize=(10,4))

def randomFunc(x):
    return x + np.random.randn(100)

ax01.plot(x, randomFunc(x), label="ax01")
ax02.plot(x, randomFunc(x), label="ax02")
ax03.plot(x, randomFunc(x), label="ax03")
ax11.plot(x, randomFunc(x), label="ax11")
ax12.plot(x, randomFunc(x), label="ax12")
ax13.plot(x, randomFunc(x), label="ax13")

plt.show()

f:id:spookyboogie:20200519235057p:plain

リストとループを用いた使用例

上記の例では,すべてのaxに対して別々の変数を割り当てましたが,下記のようにplt.subplotsから返ってくる複数のインスタンスを含むタプルに対して直接操作を加えることもできます.

import matplotlib.pyplot as put
import numpy as np

x = np.linspace(0, 10, 100) # 0〜10を100分割

row, column = 2, 3 # 行と列

# figureインスタンスとaxインスタンス群(2x3のタプル)を取得
fig, axes = plt.subplots(row, column, figsize=(12, 8)) 

# 関数リスト
funcs = [["sin", "cos", "tan"], ["exp", "log", "log10"]]

# プロット
for i in range(row):
    for j in range(column):
        # 半ば無理矢理 各axにプロット
        exec("axes[" + str(i) + "][" + str(j) + "].plot(x, np." + funcs[i][j] + "(x), label=\"" + funcs[i][j]+ "\")")
        axes[i][j].set_title(funcs[i][j]) # タイトルをつける

plt.show() # 表示

f:id:spookyboogie:20200520000430p:plain
リストとループを用いて効率化を図ろうともがいた結果,exec辺りがものすごく汚いコードになってしまいました.
このコードにどういう使い道があるかは自分でもよくわかりませんが,こんな書き方もできるということを知るのも大事ですね(?).

フィギュアに関しては,グラフのタイトルや横軸が他のグラフに干渉しないよう大きめのサイズにしたため若干余白が多いですが,うまく表示されています.

fig.add_subplot

適宜グラフを追加していくこともできます.

  • Figureクラスのインスタンス生成
  • 同クラスのadd_subplotメソッドを実行(グラフのレイアウト,位置を指定してaxを生み出す)
import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0, 10, 100)

# Figureクラスのインスタンスを生成
fig = plt.figure()

def randomFunc(x):
    return x + np.random.randn(100) + 5*np.sin(x)

# 以下,4つのサブプロットを追加
ax1 = fig.add_subplot(2, 2, 1)
ax1.plot(x, randomFunc(x))

ax2 = fig.add_subplot(2, 2, 2)
ax2.plot(x, randomFunc(x))

ax3 = fig.add_subplot(2, 2, 3)
ax3.plot(x, randomFunc(x))

ax4 = fig.add_subplot(2, 2, 4)
ax4.plot(x, randomFunc(x))

plt.show()

f:id:spookyboogie:20200520024722p:plain

figureメソッドはplt.subplotsと同様に,figsize=(12, 8)のようにしてフィギュア全体のサイズを指定できます(今回はしていません).

add_subplotの引数は,add_subplot(行, 列, 番号)です.
番号というのは,3行4列を例に取ると,
[1] [2] [3] [4]
[5] [6] [7] [8]
[9][10]11][12]
という順です.(端的に言えば左上スタート右下ゴールの順番です).
ちなみに,例えばadd_subplot(1, 1, 1)はadd_subplot(111)のように書くこともできます.

また,例えば上の例で4つ目のグラフのみ,

ax4 = fig.add_subplot(3, 3, 8)
ax4.plot(x, randomFunc(x))

としたとします.そうするとフィギュアは
f:id:spookyboogie:20200520030824p:plain
のようになります.見ればわかると思いますが,4つ目のグラフだけが3行3列を基準にした8番目に配置されています.
別のグラフへ重なって表示されてしまいますので,行・列の指定や番号の指定ミスには注意したいですね.

参考:matplotlib基礎 | figureやaxesでのグラフのレイアウト - Qiita

補足メモ

グラフの横幅や縦幅を変更したい場合は,

ax.set_xlim(0, 1000)
ax.set_ylim(0, 800)

というふうに指定する.

exec()

exec()関数は,与えられた文をそのまま実行する関数です.
リストのインデックスや,funcsの中身を埋め込んで実行するために使いましたが,コードは酷い有様です.もっとスマートな書き方がありそうですね….

eval()という似た関数もあって,違いは以下のページが丁寧に解説なさっています.
Python の eval と exec - Qiita

Tensorflow2 Subclassing APIの自分用メモ

参考:エキスパートのための TensorFlow 2.0 入門  |  TensorFlow Core
の一連の流れを自分用備忘録としてメモします.

インポート

import tensorflow as tf

from tensorflow.keras.layers import Dense, Flatten, Conv2D
from tensorflow.keras import Model

MNISTデータのロード

mnist = tf.keras.datasets.mnist

(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0

# Add a channels dimension
x_train = x_train[..., tf.newaxis]
x_test = x_test[..., tf.newaxis]

下2行は次元を追加しています.
(60000, 28, 28) → (60000, 28, 28, 1)

データの整形

# 画像データをtf.data.Datasetオブジェクトに変換
train_ds = tf.data.Dataset.from_tensor_slices((x_train, y_train)).shuffle(10000).batch(32)

# ラベルデータをtf.data.Datasetオブジェクトに変換
test_ds = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(32)

わざわざtf.data.Dataset.from_tensor_slicesに食わせているのは,Datasetオブジェクトに変換してshuffle()及びbatch()を適用するため.

shuffle()は文字通りデータをシャッフルしますが,含まれるデータ数以上の数値を入力することで完全なシャッフルが可能となります.

参考:TensorFlowのDataset.shuffleのbuffer_size - Qiita

尚,batch後にshuffleを実行すると,シャッフルはバッチ内でのみ行われ,ミニバッチの枠を超えて(例:ミニバッチAとミニバッチB間のデータでシャッフル)行われることはありません.

モデルの定義(Modelクラスのサブクラス化)

class MyModel(Model): # tensorflow.keras.Modelを継承
    def __init__(self):
        super(MyModel, self).__init__() # 親クラスのコンストラクタ実行(引数にクラス名忘れない)

        # 層を変数に格納する
        self.conv1 = Conv2D(32, 3, activation='relu')
        self.flatten = Flatten()
        self.d1 = Dense(128, activation='relu')
        self.d2 = Dense(10, activation='softmax')

    def call(self, x): # xはモデルへの入力データ
        # 構造(層の繋がり)を定義する
        x = self.conv1(x)
        x = self.flatten(x)
        x = self.d1(x)
        return self.d2(x)

# モデルのインスタンスを作成
model = MyModel()
メモ
  • tensorflow.keras.Modelを継承する
  • __init__内で,super(モデル名, self).__init__()
  • __init__内で,層を変数に格納する
  • call内で,構造(層の繋がり)を定義する
  • call(self, x)のxは入力データ

損失関数と最適化手法の決定

# 変数名はloss_objectとするのが通例らしい
loss_object = tf.keras.losses.SparseCategoricalCrossentropy() # 損失関数

optimizer = tf.keras.optimizers.Adam() # 最適化手法
sparse_categorical_crossentropyとcategorical_crossentropyの違いは?

sparse_categorical_crossentropy:ラベルが整数表記
categorical_crossentropy:ラベルがone-hot表記

参考:categorical_crossentropyとsparse_categorical_crossentropyの違い【Keras】 | 研究所で働くエンジニアのブログ

5/30追記: 上記の関係が逆になっていました。大変申し訳ありませんでした。

metrics

# 学習用
train_loss = tf.keras.metrics.Mean(name='train_loss')
train_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(name='train_accuracy')

# テスト用
test_loss = tf.keras.metrics.Mean(name='test_loss')
test_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(name='test_accuracy')

metricsとは,モデルの評価指標です.
ここでは損失関数の値と分類の精度を指標としています.

学習用関数定義

@tf.function # デコレータ
def train_step(images, labels):
    with tf.GradientTape() as tape:
        predictions = model(images) # モデルに入力を与え,予測値を求める
        loss = loss_object(labels, predictions) # 損失関数にラベルと予測値を与え,lossを計算
    gradients = tape.gradient(loss, model.trainable_variables) # 自動微分で勾配を求める
    optimizer.apply_gradients(zip(gradients, model.trainable_variables)) # 勾配と学習用変数から,重みを更新

    train_loss(loss) # 損失関数の値を記録(Mean)
    train_accuracy(labels, predictions) # 精度を記録(SparseCategoricalAccuracy)

Model.trainable_variables…ネットワーク内の訓練可能な重みをリストで取得

tf.keras.metrics.Mean

Meanはその名の通り,与えられたデータの平均値を求めます.

# 平均を計算
m = tf.keras.metrics.Mean()
m([1,2,3,4,5]).numpy() # 3.0

# 値の蓄積
m.reset_states() # 初期化
_ = m.update_state([2,4,6,8,10])
_ = m.update_state([10, 20, 30, 40, 50]) # 計算の蓄積
m.result().numpy() # 18.0 = (2+4+6+8+10+10+20+30+40+50)/10

# 計算を適用する値の選択
m.reset_states()
_ = m.update_state([10, 20, 30, 40, 50], sample_weight=[1,1,0,0,0]) # 0に対応する値は計算されない
m.result().numpy() # 15.0

つまり,train_loss(loss)という部分では,loss_object(labels, predictions)から返ってきた32個(ミニバッチサイズ)のデータから成るlossを平均していると思われます(ミニバッチ学習).
(詳細な挙動について分かり次第追記します…)

tf.keras.metrics.SparseCategoricalAccuracy

モデルの精度を計算します.

m = tf.keras.metrics.SparseCategoricalAccuracy()
_ = m.update_state([[2],[1]], [[0.1, 0.9, 0.8], [0.05, 0.95, 0]])
m.result().numpy() # 0.5

つまり,train_accuracy(labels, predictions)の部分では,正解ラベルとモデルの出力(確率)を渡してミニバッチ単位での精度を求めています.

テスト用関数定義

学習用と殆ど変わりません.
データがテスト用のものに変わっただけです.

@tf.function
def test_step(images, labels):
    predictions = model(images)
    t_loss = loss_object(labels, predictions)

    test_loss(t_loss)
    test_accuracy(labels, predictions)
補足
    for images, labels in train_ds:
        train_step(images, labels)

    for test_images, test_labels in test_ds:
        test_step(test_images, test_labels)

すぐ下に記述されていますが,実際の学習では,上記のようにデータが与えられます.
train_dsは最初にbatch(32)でデータ分けをしていますから,
train_stepに与えられるimagesとlabelsの形状は,imagesが(32, 28, 28, 1),labelsが(32,)です(test_images,test_labelsも同様).

学習とテスト

EPOCHS = 5 # エポック数(※1エポック = 少なくとも一度は全てのデータを学習に使用した状態)

for epoch in range(EPOCHS): # 5エポック
    for images, labels in train_ds: # ミニバッチ学習
        train_step(images, labels) # 32個で1セットのデータ

    for test_images, test_labels in test_ds: # ミニバッチごとに精度を計算
        test_step(test_images, test_labels) # 32個で1セットのデータ

    # エポック数,loss,精度を表示
    template = 'Epoch {}, Loss: {}, Accuracy: {}, Test Loss: {}, Test Accuracy: {}'
    print (template.format(epoch+1,
                           train_loss.result(),
                           train_accuracy.result()*100,
                           test_loss.result(),
                           test_accuracy.result()*100))
  
    # 次のエポック用にメトリクスをリセット
    train_loss.reset_states()
    train_accuracy.reset_states()
    test_loss.reset_states()
    test_accuracy.reset_states()

train_stepのループではデータ数60,000を32のミニバッチサイズで割るので,1,875回
test_stepのループではデータ数10,000を32のミニバッチサイズで割るので,約313回
ループが回ることになります.

注意点

Subclassingの書き方では,モデルの概要を表示するmodel.summary()は実行できません.
Subclassing APIは,Define-by-Runである(実行と同時にモデルが動的に生成される)ためです.

【Python文法】__call__の呼び出し方

__init__と同じように,外部から直接参照されない書き方(アンダースコア2つで囲む)で記述される__call__はどのように使うのでしょうか?

class A:
    def __init__(self, a):
        self.a = a

    def __call__(self):
        print(self.a)

hoge = A(10)
hoge()

見てわかるようにインスタンスを格納した変数()』という記述をすることで,クラス内の__call__関数を呼び出すことができます.
勿論,本来の関数と同じように,引数の有無は__call__の定義次第です.

継承

また,クラスの継承時に子クラスが親クラスの関数を利用できるようになるのと同様に,子クラスは継承した親クラスの__call__関数を利用できます.

class A:
    def __init__(self, a):
        self.a = a

    def __call__(self):
        print(self.a)

class B(A):
    def __init__(self, b):
        super().__init__(b) # self.aの初期化用


hoge = B(20)
hoge() # 20

よりわかりやすい例(余分なものを排除)

class A:
    def __init__(self):
        pass

    def __call__(self):
        print("Hello, world!")

class B(A):
    def __init__(self):
        pass

hoge = B()
hoge() # Hello, world!

Tensorflow layersの重みの初期化

tensorflow.keras.initializersをインポートし,layers APIのkernel_initializerに任意のクラスを引数として与えると,条件(定数や確率分布)に従って重みを初期化できます.
初期化 - Keras Documentation

  • keras.initializers.Zeros()
    • すべての重みを0で初期化
  • keras.initializers.Ones()
    • すべての重みを1で初期化
  • keras.initializers.Constant(value=0)
    • すべての重みを定数で初期化.デフォルトは0(Zeros()と同じ挙動).
  • keras.initializers.RandomNormal(mean=0.0, stddev=0.05, seed=None)
  • keras.initializers.RandomUniform(minval=-0.05, maxval=0.05, seed=None)
    • すべての重みを一様分布に従って初期化.乱数の下限及び上限,seedが指定可能.
  • keras.initializers.TruncatedNormal(mean=0.0, stddev=0.05, seed=None)
使用例
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import initializers

inputs = keras.layers.Input(shape=(28*28, ))
x = keras.layers.Dense(1024,
                       activation='relu',
                       kernel_initializer=initializers.TruncatedNormal())(input)
outputs = keras.layers.Dense(10, activation='softmax')

注意点

ZerosやOnesを記載してから言うのもなんですが,重みの初期値を均一な値にすることは避けるべきです.
なぜなら,順伝播時には次の層への出力がすべての同じ値になってしまい,誤差逆伝播時にはすべての重みの値が同じように更新されてしまい(重みが同じ値を持ってしまい),大量の重みを使う利点が損なわれてしまうからです.

入力層(ノード数3)→隠れ層(ノード数4)間の重みをすべて1とした場合を考えると,
{
\begin{pmatrix}
1&2&3
\end{pmatrix}

\begin{pmatrix}
1&1&1&1\\
1&1&1&1\\
1&1&1&1
\end{pmatrix}
=
\begin{pmatrix}
6&6&6&6
\end{pmatrix}
}

まとめ

便利な呪文
kernel_initializer=initializers.TruncatedNormal()

【手順メモ】Windows10にTensorflow gpu 2.0をインストール【自分用】

概要

Build from source on Windows  |  TensorFlow
ここを参考にバージョンをあわせて,以下を実行.

  1. pipによるTensorflow 2.0のインストール
  2. CUDAのインストール
  3. CuDNNのインストール
  4. PATHを通す

ちなみにPythonのバージョンは,3.6.8です.

Tensorflow2.0のインストール

pip install tensorflow-gpu==2.0.0

待つ!以上!

別バージョンが既に入っていたらアンインストール
pip uninstall tensorflow-gpu

CUDAのインストール

CUDA Toolkit Archive | NVIDIA Developer
ここから『CUDA Toolkit 10.0 (Sept 2018)』を選択し,
Windows -> x86_64 -> 10 -> exe(local) でダウンロード.

ダウンロード後,exeファイルを実行し,次へ進む連打でインストール.

この段階で,C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.0が生成されている(はず).

cuDNN

cuDNN Archive | NVIDIA Developer
ここから『Download cuDNN v7.4.1 (Nov 8, 2018), for CUDA 10.0』をダウンロード.
解凍したら,中身のbin, include, libを先程のフォルダ(C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.0)へ上書き保存.

PATHを通す

コントロールパネル -> システム -> システムの詳細設定 -> 環境変数 -> "Path"を編集
以下を追加

正しく実行できるか確認

import tensorflow as tf
print(tf.__version__) # 2.0.0

Python クラスの継承 super().__init()__

変数の上書き

super().__init__()は,スーパークラスのコンストラクタを実行するための命令です.
下記の例を見てみましょう.

class Parent:
    def __init__(self):
        self.test_num = 10

    def show_num(self):
        print(self.test_num)

class Child(Parent):
    def __init__(self, num):
        self.test_num = num
        super().__init__()


child1 = Child(5)
child1.show_num() # 10

Childクラスのコンストラクタ内で,self.test_numに5を代入しています.
その下でsuper().__init__()と,スーパークラスのコンストラクタを実行しています.

するとどうなるでしょうか?
このプログラムを実行すると,10が表示されます.

child1のself.test_numは5じゃないの?と思うかもしれませんが,これがsuper().__init()__の働きです.

スーパークラスのコンストラクタを見ると,同様にself.test_numに対して値を代入していますね.
つまり,self.test_num = 10という処理を行うコンストラクタをChildクラス内で実行したことで,値が上書きされたのです.
イメージとしては以下のように,Childクラス内にスーパークラスのコンストラクタ処理が埋め込まれたような感じでしょうか.

class Child(Parent):
    def __init__(self, num):
        self.test_num = num
        self.test_num = 10 # 埋め込まれ,値を上書き

勿論,super().__init__()をコメントアウトすると5が表示されます.

親クラスでのみ定義した変数も利用可能に

次は以下のような例を考えてみます.単純化のためにスーパークラスはコンストラクタ以外に関数を持たせていません.

class Parent:
    def __init__(self):
        self.parent_num = 20 # 親独自の変数をコンストラクタ内で定義する

class Child(Parent):
    def __init__(self):
        pass
        # super().__init__()
    
    def show_parent_num(self):
        print(self.parent_num)


child1 = Child()
child1.show_parent_num()

この状態でプログラムを実行すると,『AttributeError: 'Child' object has no attribute 'parent_num'』と表示され,Childオブジェクトはparent_numという変数を持っていないとエラーが表示されます.
当たり前ですね.スーパークラス内でしか定義されていない変数ですから,Child内から参照できるはずがありません(クラスの継承によって引き継がれるのはあくまで関数の処理のみ).

そこで,super().__init__()のコメントアウトを外し,passを消してみます.
すると,20が表示されます.これも先述のように埋め込まれた形で説明できますね.

class Child(Parent):
    def __init__(self):
        super().__init__()

class Child(Parent):
    def __init__(self):
        self.parent_num = 20

こうなることで,Child内のshow_parent_numメソッドから,まさに自分自身のself.parent_numを呼び出した,と自然な解釈が可能です.