PyTorch深層学習③テンソル:NumPy編
前回はテンソルの概念を解説しました。今回は、NumPyを使ってテンソルの計算を実践しましょう。
しかし、こう思われるかもしれません。「PyTorchの話なのだから、PyTorchのTensor(テンソル)を学ぶべきでは?」
本シリーズの記事リスト
③テンソル:NumPy編
PyTorchのTensorは確かに重要なのですが、NumPyをある程度知っておくと役に立ちます。なぜなら、Pythonで取り扱う多くのライブラリはNumPy形式のデータをサポートしているからです。
例えば、以下のようなライブラリはNumPyと互換性があります。
画像系: OpenCV、Matplotlib、Pillow など
データサイエンス: Pandas
機械学習: scikit-learn
そして、もちろんPyTorchもNumPyのデータを扱えます。多くのデータセットはNumPy形式でセーブされています、特に画像系はそうです。
また、PyTorchの Tensor で使える関数は NumPy の関数とよく似ています。これは意図的にそうなっていると思われます。なぜなら、そうすることでNumPyを知っている人間ならPyTorchの Tensor に対しての学習コストが低くなるからです。わざわざ独特な仕様にする必要はないからです。
なのでNumPyを学んでおくとPyTorchの Tensor を学ぶのがスムーズになります。では、さっそく始めましょう。
NumPyのインストール
さて、NumPyは「Numerical Python」の略です。Numericalは「数値的な」という意味ですが、要するにNumPyは数値計算を得意とするライブラリであり、テンソル(n次元配列と言ったりもします)を扱いやすくしてくれます。
画像系、データサイエンス、機械学習などをPythonで扱う人は、NumPyに触れることが多いでしょう。ただし、NumPyはPythonの標準ライブラリではありません。よって、最初にインストールをする必要があります。
Pythonの仮想環境を作ってNumPyとJupyterをインストールします。Jupyterはインストールしなくとも良いですが、NumPyのいろんな機能を試すのにやりやすいのでお勧めします。また、後にPyTorchを使う際にもJupyterを使う予定です。
# 適当にフォルダを作って移動する
mkdir learn_numpy
cd learn_numpy
# Pythonの仮想環境を作る
python3 -m venv venv
source venv/bin/activate
# pipを最新にしておく
pip install --upgrade pip
# NumPyとJupyterをインストール
pip install numpy jupyter
Jupyterノートブック
Jupyterを使わなくともコマンドラインで全て行うことも可能です。ここでは、Jupyterを使ってみたい人向けに簡単に使い方を解説します。
Jupyterについては説明するよりもやってみる方が早いので次のようにして実行してください。なお、先ほど設定したPythonの仮想環境の中で実行する必要があります。
jupyter notebook
すると、ブラウザーが立ち上がって次のようなページが開きます。
venv と見えるのは現在のディレクトリのフォルダを表示しているからです。まず、新しいノートブックを作ります。NewをクリックしてPython 3 (ipykernel) を選んでください。
すると新しいNotebookが表示されます。Untitledとなっているのはファイル名なのですが、クリックすれば変更できます。
とりあえずNumPyとしておきます。
変えなくともよいですが、後でファイルを見てわかりやすいように変えておくとよいでしょう。
ではNumPyをインポートしましょう。np と短縮した名前でインポートします。もちろん、numpy の名前のままでもいいですが、この方が短いし多くの人がそうするので同様にするのがいいと思います。
上図のように四角の中に書くのですが、この四角をセルと呼びます。セルの中にカーソルがある状態で、Run ボタンを押すと実行されます。または、CMD+ENTER(macOS)か CTRL+ENTER(Windows)でも実行できます。
実行すると下に新しいセルが自動的に挿入されます。そこに次に実行したい内容をPythonのコードで書いてまた実行します。
まずは、2次元のテンソルを作ってみましょう。Pythonのリストを使うとしたらこんな風に定義します。ここからは画像ではなくテキストで書きます。その方がコピペしやすいので。
# Pythonのリストで2次元配列を表現する
values = [ [1, 2, 3], [4, 5, 6] ]
values
最後にvaluesと書いておいて実行するとvaluesの中身がプリントされます。
このように、Jupyterノートブックは途中で実行し結果を確認できるのが利点です。
NumPyを使ってみる
さて、次のように書いた方が2次元テンソルぽいかもしれません。見た目は違いますが、中身は同じことです。
values = [
[1, 2, 3],
[4, 5, 6]
]
values
例えば、values[1][2] と書けば6が返されます。2次元テンソルぽいですよね。このようにPythonのリストで2次元テンソルを表現できるのであれば、なぜNumPyを学ぶ必要があるのでしょうか。
実は、Pythonのリストは数字やオブジェクトなどをリストに収納するものであってテンソルを使った計算には向いていません。上記の2次元テンソルは二つのリスト(オブジェクト)をリスト内に収納しているだけです。
values[1][2] という表現は、values[1] が2番目のリスト[4, 5, 6] を返し、それに対して values[1][2] が2番目のリストの中の3番目の数字を返しているだけです。
よって、次のようなことはできません。エラーになります。
values += 1
リストにおける + の意味は、リストに他のリストを連結することであり、数値の足し算ではありません。
そこでNumPyが役に立ちます。
まず、NumPyのオブジェクトを作ってみましょう。Pythonのリストで表現した2次元テンソルをそのままNumPyの配列(Array)として表現します。
x = np.array(values)
x
これを実行すると次のようにNumPyのオブジェクトが出力されます。
NumPyではテンソルのことを array と呼びます。これに1を足してみましょう。
x + 1
実行すると次のように全ての要素に1が加算されます。
ここでは、x 自体は変わっていませんが、x に1を足したものが返されるので、それが表示されています。x だけを次のセルに書いて実行すると元の亜害が表示されます。
このように元の値は変更されていません。しかし、次のように書くと元の値が変更されます。
x += 1
x
x[1, 2] と書いて実行すると7が返されます。Pythonのリストと表記が違うことに注目してください。Pythonのリストだとx[1][2]のような表記をしました。それは、リストの中のリストの中の数値を取り出していたからです。
NumPyでは全ての数値が一つのオブジェクトの中で処理されており、x[1, 2]といった簡潔な書き方をすることができます。
次のように書くと1行目だけがNumPyの array として返されます。
x[0]
足し算だけでなく掛け算やベクトルの内積や行列の積などさまざまな計算が可能です。
NumPyのデータ型
データ型を見るには、次のように dtype という属性を表示します。
x.dtype
浮動小数点型であるフロート型のNumPy array を作るには次のようにします。
np.array(values, dtype = np.float32)
なお、Pythonでは数値にドットをつけると浮動小数点型になるので次のように定義することも可能です。
float_values = [
[1., 2., 3.],
[4., 5., 6]
]
x = np.array(float_values)
x
今回は、dtype が出力に含まれていません。なぜでしょうか。dtype を確認しましょう。
x.dtype
NumPyではデフォルトの浮動小数点型は64ビットの float64 になっています。なので float32 とデフォルトでない型を指定したときは、出力した時に表示されます。また、デフォルトの整数型は64ビットの int64 です。
データ型については注意する必要があります。PyTorchではデフォルトは32ビットのフロート型を使うことが多くNumPyで64ビットとして収納されているものを32ビットに変換する必要があることがあります。これについてはのちのちにふれていきます。
NumPy arrayを作る様々な方法
NumPy array(あるいはNumPyオブジェクトとも呼びます)を作るための便利な関数がたくさんあります。ここでは代表的なものをいくつか紹介します。
np.empty は中身の数値を指定せずにオブジェクトを生成します。これはあらかじめ値がわかっていないけど後で指定する用に作っておきたい時に便利です。
np.empty((2, 3))
これを実行すると、その中身を見ることができます。
np.emptyはあるメモリ上の場所を確保するのですが、そこにたまたまあった値を表示しています。おそらく np.array(values, dtype = np.float32) で使ったメモリが解放され、残っていた数値がそのまま表示されていると思われます。よって、どのような値が表示されるのかが決まっているわけではありません。
np.zeros はNumPyオブジェクトを作る際に全ての要素を0に設定します。以下では、配列のサイズを(2, 3)と指定しており縦2横3の2次元テンソルになります。
np.zeros((2, 3))
np.ones はNumPyオブジェクトを作る際に全ての要素を1に設定します。
np.ones((2, 3))
np.random.rand は要素をランダムな数値に設定します。乱数は一様分布で、0以上1未満の値が返されます。
np.random.rand(2, 3)
同じことを繰り返すたびに異なる値が返されます。
np.random.seed は乱数のシードを固定することで再現性を確保します。シードの値はなんでもよいのでここでは123としています。
np.random.seed(123)
np.random.rand(2, 3)
これを2回続けて実行してみます。
2度とも同じ値が返されているのが確認できます。
np.zeros_like は他のNumPyオブジェクトと同じ形(2次元テンソルなら縦横の数が同じに、3次元なら3つの軸の数など。英語ではShapeと呼ぶ)で全ての要素がゼロに設定されたものを作ります。
x = np.array(values)
np.zeros_like(x)
なお、データ型(dtype)は values から引き継いでいます。仮に、values が float32型であれば、np.zeros_like で作られたオブジェクトの方も float32 になります。
np.ones_like では全ての要素が1に設定されます。
np.ones_like(x)
np.eye は対角行列で指定された数の1を対角線上に持つNumPyオブジェクトを作ります。
np.eye(3)
np.eye でで異性されるオブジェクトの型は float64 です。
NumPyのオブジェクトのShape
shape はテンソルの各次元の軸数を返します。
x = np.ones((2, 3))
x.shape
このNumPyオブジェクトは2次元で、1次元目(縦)に2つの軸があり、2次元目(横)に3つの軸があります。そもそも np.ones でオブジェクトを作成する際に指定しているので当たり前ですが。
よくテンソルを使った計算をしているときにShapeが噛み合わなくて計算ができなかったりすることがあります。その際にShapeを確認してデバッグしたりします。
なお、Shapeを変更することもできます。
x.reshape(3, 2)
これで2x3から3x2に変更されました。要素数が同じになるならばShapeを変更できます。
x.reshape(1, 6)
ここでは1x6に変更されましたが、要素数が6なので問題ありません。
最後に3次元テンソルを作ってみましょう。
x = np.ones((2, 3, 4))
x
このテンソルのShapeは(2, 3, 4)です。つまり、(3, 4)の2次元テンソルが2つ入っていると考えることができます。
よって、最初の2次元テンソルは次のように返すことができます。
x[0]
このようにテンソルを生成する際の次元数はいくらでも増やすことができます。
まとめ
以上、NumPyの機能をいくつか紹介しました。Jupyterノートブックを使いながら自分で実行してみるとすぐになれると思います。ゼロからイチへ進むことができればあとはなんとかなるものです。
もちろん、JupyterもNumPyも機能がまだまだたくさんあるのでGoogle検索したりChatGPTに聞いてみてください。あるいは新しいBingか。それともGitHub Copilotか。いつの間にか随分と便利な世の中になりました。
次回はPyTorchのテンソルを使って同様な練習をしましょう。
(続く)
この記事が気に入ったらサポートをしてみませんか?