テンソルと演算

View on TensorFlow.org Run in Google Colab View source on GitHub

これは、下記の手法を示す TensorFlow の入門チュートリアルです。

  • 必要なパッケージのインポート
  • テンソルの作成と使用
  • GPUによる高速化の使用
  • tf.data.Datasetのデモ
from __future__ import absolute_import, division, print_function, unicode_literals

!pip install -q tensorflow-gpu==2.0.0-beta1

TensorFlowのインポート

はじめに、tensorflow モジュールをインポートします。TensorFlow 2.0 では、eager execution が既定でオンとなっています。 これにより、TensorFlow のフロントエンドがよりインタラクティブになります。詳細は後述します。

import tensorflow as tf

テンソル

テンソルは多次元配列です。NumPy の ndarray オブジェクトと同様に、tf.Tensor にはデータ型と形状があります。これに加えて、tf.Tensor は( GPU のような)アクセラレータのメモリに置くことができます。TensorFlow には、tf.Tensor を使用し生成するたくさんの演算(tf.add, tf.matmul, tf.linalg.inv など)のライブラリが存在します。これらの演算では、ネイティブな Python データ型が自動変換されます。例を示します。

print(tf.add(1, 2))
print(tf.add([1, 2], [3, 4]))
print(tf.square(5))
print(tf.reduce_sum([1, 2, 3]))

# Operator overloading is also supported
print(tf.square(2) + tf.square(3))
tf.Tensor(3, shape=(), dtype=int32)
tf.Tensor([4 6], shape=(2,), dtype=int32)
tf.Tensor(25, shape=(), dtype=int32)
tf.Tensor(6, shape=(), dtype=int32)
tf.Tensor(13, shape=(), dtype=int32)

それぞれのtf.Tensorには、形状とデータ型があります。

x = tf.matmul([[1]], [[2, 3]])
print(x)
print(x.shape)
print(x.dtype)
tf.Tensor([[2 3]], shape=(1, 2), dtype=int32)
(1, 2)
<dtype: 'int32'>

NumPy 配列と tf.Tensor の間のもっとも明確な違いは

  1. テンソルは( GPU や TPU などの)アクセラレータメモリを使用できる
  2. テンソルは変更不可

NumPy互換性

TensorFlow のtf.Tensorと NumPy の ndarray 間の変換は簡単です。

  • TensorFlow の演算により NumPy の ndarray は自動的にテンソルに変換される
  • NumPy の演算によりテンソルは自動的に NumPy の ndarray に変換される

テンソルは .numpy() メソッドを使って明示的に NumPy の ndarray に変換されます。NumPy のndarray と tf.Tensor はその下敷きとなるメモリ上の表現が、できるかぎり共通化されているので、通常この変換のコストは小さいです。しかし、NumPy 配列はホスト側のメモリに置かれる一方、tf.Tensor はGPU のメモリに置かれる可能性もあるため、下層の表現をいつも共通化できるとは限りません。また、変換にはGPU からホスト側メモリへのコピーも関わってきます。

import numpy as np

ndarray = np.ones([3, 3])

print("TensorFlow演算によりnumpy配列は自動的にテンソルに変換される")
tensor = tf.multiply(ndarray, 42)
print(tensor)


print("またNumPy演算によりテンソルは自動的にnumpy配列に変換される")
print(np.add(tensor, 1))

print(".numpy()メソッドによりテンソルは明示的にnumpy配列に変換される")
print(tensor.numpy())
TensorFlow演算によりnumpy配列は自動的にテンソルに変換される
tf.Tensor(
[[42. 42. 42.]
 [42. 42. 42.]
 [42. 42. 42.]], shape=(3, 3), dtype=float64)
またNumPy演算によりテンソルは自動的にnumpy配列に変換される
[[43. 43. 43.]
 [43. 43. 43.]
 [43. 43. 43.]]
.numpy()メソッドによりテンソルは明示的にnumpy配列に変換される
[[42. 42. 42.]
 [42. 42. 42.]
 [42. 42. 42.]]

GPU による高速化

TensorFlow の演算の多くは、GPU を計算に使用することで高速化されます。TensorFlow は演算に注釈をつけなくとも、自動的に GPU と CPU のどちらかを選択し、必要であればテンソルを GPU メモリと CPU メモリの間でコピーして実行します。演算で生成されたテンソルは通常演算を実行したデバイスのメモリに置かれます。例を見てみましょう。

x = tf.random.uniform([3, 3])

print("利用できるGPUはあるか: "),
print(tf.test.is_gpu_available())

print("テンソルはGPU #0にあるか:  "),
print(x.device.endswith('GPU:0'))
利用できるGPUはあるか: 
True
テンソルはGPU #0にあるか:  
True

デバイス名

Tensor.device プロパティにより、そのテンソルの内容を保持しているデバイスの完全な名前文字列を得ることができます。この名前には、プログラムを実行中のホストのネットワークアドレスや、ホスト上のデバイスについての詳細がエンコードされています。この情報は、TensorFlow プログラムの分散実行に必要なものです。テンソルがホスト上の N 番目のGPUにある場合、文字列の最後は GPU:<N> となります。

明示的デバイス配置

TensorFlowでいう配置は、個々の演算を実行するためにどのようにデバイスにアサイン(配置)されるかを指します。前述のとおり、明示的な示唆がなければ、TensorFlow は演算を実行するデバイスを自動的に決め、必要であればテンソルをそのデバイスにコピーします。しかし、tf.device コンテキストマネジャーを使うことで、TensorFlow の演算を特定のデバイスに配置することができます。例を見てみましょう。

import time

def time_matmul(x):
  start = time.time()
  for loop in range(10):
    tf.matmul(x, x)

  result = time.time()-start
    
  print("10 loops: {:0.2f}ms".format(1000*result))

# CPUでの実行を強制
print("On CPU:")
with tf.device("CPU:0"):
  x = tf.random.uniform([1000, 1000])
  assert x.device.endswith("CPU:0")
  time_matmul(x)

# GPU #0があればその上での実行を強制
if tf.test.is_gpu_available():
  print("On GPU:")
  with tf.device("GPU:0"): # 2番めのGPUなら GPU:1, 3番目なら GPU:2 など
    x = tf.random.uniform([1000, 1000])
    assert x.device.endswith("GPU:0")
    time_matmul(x)
On CPU:
10 loops: 108.40ms
On GPU:
10 loops: 314.93ms

データセット

このセクションでは tf.data.Dataset API を使って、モデルにデータを供給するためのパイプラインを構築します。tf.data.Dataset APIは、単純で再利用可能な部品をもとに、モデルの訓練あるいは評価ループにデータを供給する高性能で複雑な入力パイプラインを構築するために使われます。

ソースDatasetの作成

Dataset.from_tensorsDataset.from_tensor_slices といったファクトリー関数または TextLineDataset あるいはTFRecordDataset のようなファイルを読み込むオブジェクトを使って、 元となるデータセットを作成しましょう。詳しくは、TensorFlow Dataset guide を参照してください。

ds_tensors = tf.data.Dataset.from_tensor_slices([1, 2, 3, 4, 5, 6])

# CSVファイルを作成
import tempfile
_, filename = tempfile.mkstemp()

with open(filename, 'w') as f:
  f.write("""Line 1
Line 2
Line 3
  """)

ds_file = tf.data.TextLineDataset(filename)

変換の適用

map, batch, shuffle などの変換関数を使って、データセットレコードに変換を適用します。

ds_tensors = ds_tensors.map(tf.square).shuffle(2).batch(2)

ds_file = ds_file.batch(2)

イテレート

tf.data.Dataset オブジェクトは、中のレコードを繰り返し利用するためのイテレーションをサポートします。

print('ds_tensors の要素:')
for x in ds_tensors:
  print(x)

print('\nds_file の要素:')
for x in ds_file:
  print(x)
ds_tensors の要素:
tf.Tensor([4 9], shape=(2,), dtype=int32)
tf.Tensor([16 25], shape=(2,), dtype=int32)
tf.Tensor([ 1 36], shape=(2,), dtype=int32)

ds_file の要素:
tf.Tensor([b'Line 1' b'Line 2'], shape=(2,), dtype=string)
tf.Tensor([b'Line 3' b'  '], shape=(2,), dtype=string)