0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PyTorchに入る前に、Pythonの参照と合成関数を確かめる(1章 前半)

0
Posted at

PyTorchに入る前に、Pythonの参照と合成関数を確かめる(1章 前半)

研究では、分布ラマン増幅の励起条件を深層展開で最適化する。数値シミュレーションで励起条件と受信SNRの対応データを作り、そこからPyTorchで励起光のパワーと周波数を決める。実装に入る前に、赤石雅典「最短コースでわかる PyTorch&深層学習プログラミング」の1章を、手を動かしながら読み直している。その記録を残す。

1章はPyTorch本体に入る前の準備にあたる。この記事ではそのうち、NumPyの代入、テンソルとの関係、合成関数まで扱う。数値微分とクラス定義は次の記事に回す。

なお、NumPyは数値計算のライブラリで、配列をまとめて高速に計算するために使う。機械学習ではデータをこの配列の形で持つことが多い。

y = x は中身を複製しない

まずこのコードを見てほしい。

import numpy as np

x = np.array([5, 7, 9])
y = x

x[1] = -1

print(x)  # [ 5 -1  9]
print(y)  # [ 5 -1  9]

yには代入以降なにも触れていない。それでもxと一緒に値が変わる。最初に見たとき、なぜyまで動くのか分からなかった。

理由はPythonの変数の持ち方にある。変数は中身そのものではなく、中身が置かれている場所を指す名札のようなものである。この「指す」という関係を参照と呼ぶ。y = xは中身を複製する命令ではなく、yという名札にもxと同じ場所を指させる命令になる。同じ場所を指しているので、片方から書き換えれば両方から見える。

id()で場所を確認できる。

x = np.array([5, 7, 9])
y = x
z = x.copy()

print(id(x) == id(y))  # True  同じ場所
print(id(x) == id(z))  # False 別の場所

切り離したいときはcopy()を挟む。これで中身が別の場所に複製され、yは独立する。

x = np.array([5, 7, 9])
y = x.copy()

x[1] = -1

print(x)  # [ 5 -1  9]
print(y)  # [5 7 9]

テンソルとNumPyの間でも同じことが起きる

ここで一つ用語を挟む。PyTorchが扱うデータはテンソルと呼ばれる形式で持つ。中身はNumPyの配列とよく似た多次元の数値の並びで、これにGPUでの計算や自動微分といったPyTorch向けの機能が付いたものだと思えばよい。

テンソルとNumPy配列は互いに変換できる。ところが、numpy()で変換して作った配列は、元のテンソルと同じ場所を指したままになる。

import torch

x1 = torch.ones(5)          # 1 が5個並んだテンソル
x2 = x1.data.numpy()        # テンソルからNumPy配列に変換

x1[1] = -1

print(x1)  # tensor([ 1., -1.,  1.,  1.,  1.])
print(x2)  # [ 1. -1.  1.  1.  1.]

テンソルとNumPyはクラスが違うので、別物として扱ってしまいそうになる。それでも指している場所は同じで、片方を書き換えれば連動する。対策は先ほどと同じで、copy()を付ければ切れる。

x2 = x1.data.numpy().copy()

自分の研究に当てはめてみる。シミュレーションで作った励起条件とSNRのデータはNumPy配列で持ち、学習に渡すところでテンソルに変換する。前処理で正規化をかけたつもりが、この連動で元の配列まで書き換わっていたとする。学習が収束しない原因を学習側から探しても、まず見つからない。1章の最初にこの話が置かれている理由が分かる。

合成関数は、関数をつないだもの

合成関数という言葉を先に確認する。ある関数の出力を、そのまま別の関数の入力に渡してつないだものを合成関数と呼ぶ。

本では f(x) = 2x^2 + 2 を、あえて三つの小さな関数に分解する。

$$f_1(x) = x^2,\quad f_2(x) = 2x,\quad f_3(x) = x + 2$$

これをPythonで書くと次のようになる。

def f1(x):
    return x ** 2

def f2(x):
    return x * 2

def f3(x):
    return x + 2

x1 = f1(x)      # x を2乗
x2 = f2(x1)     # その結果を2倍
y  = f3(x2)     # さらに2を足す

x を入れて x1x1 を入れて x2x2 を入れて y。前の出力を次の入力にそのまま渡している。これが合成関数の実装の形である。

なぜこんな当たり前のことを分解するのか。ニューラルネットワークの中身が、まさにこの形だからである。層を一つの関数と見れば、ネットワーク全体は関数をいくつもつないだ合成関数になる。この代入の連鎖に見慣れておくと、後で出てくるコードが読みやすくなる。

自分の研究の深層展開も同じ見方ができる。深層展開は繰り返し計算を有限回で区切り、各回のパラメータを学習で決める手法である。1回分を関数と見れば、全体はやはり関数を並べた合成関数になる。学習でパラメータを調整できるのは、この合成関数をさかのぼって調整しているからだ、と読める。

まとめ

1章の前半で確認したことを振り返る。

Pythonの変数は中身そのものではなく、中身のある場所を指す名札である。だから y = x では中身が複製されず、片方の変更が両方に及ぶ。独立させたいときは copy() を挟む。

テンソルはPyTorchが扱うデータ形式で、NumPy配列に計算機能を足したものと考えればよい。テンソルとNumPyの間で変換したときも、同じ場所を指したまま連動することがある。ここでも copy() で切れる。

合成関数は、ある関数の出力を次の関数の入力につないだものである。Pythonでは前の出力を次に渡す代入の連鎖として書ける。ニューラルネットワークの中身はこの形をしている。

参照の共有は、機械学習のデータ前処理でそのまま事故につながる。合成関数は、この先のモデル実装を読むための土台になる。どちらも派手さはないが、飛ばすと後で効いてくる部分だった。

次の記事では、1章の残りである数値微分とクラス定義を扱う。


参考書籍:赤石雅典「最短コースでわかる PyTorch&深層学習プログラミング」日経BP

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?