Mojo言語 〜Tensor型〜


MojoのTensor型にいじめられた

公式のリファレンスを見ながらTensor型で遊んだらよくわからんかった。
と言うことで、今回遊んでみてわかったTensor型の使い方をまとめます。
公式のTensor型のリファレンスは以下で確認できます。
具体例で示したコードはコピペで動くはずです。

ちなみに、下記ページでMojoをすぐに動かして遊べます。




とりあえずTensor型を使う

一旦はTensor型を使ってみよう。


import

まずはtensorをインポートします。

from tensor import Tensor

これでTensor型の値を扱えるようになります。


宣言

以下のコードでTensor型を宣言できます。

from tensor import Tensor


fn main():
    var x = Tensor[DType.float32](3)
    print(x)

結果は以下のように返ってくるはず。

Tensor([[0.0, 0.0, 0.0]], dtype=float32, shape=3)

宣言箇所を詳しく見ると、

var x = Tensor[配列の中身の型](配列の形状)

と言う構成になってます。


要素を取得

これは基本的にPythonと同じようにアクセスできます。

from tensor import Tensor


fn main():
    var x = Tensor[DType.float32](3)
    print(x[0])
    print(x[1])
    print(x[2])

結果は以下の通り。

0.0
0.0
0.0

Tensorのインデックスを指定すれば要素の値を取得できます。


値の代入

代入もPythonと同じ流れで可能です。

from tensor import Tensor


fn main():
    var x = Tensor[DType.float32](3)
    x[0] = 1
    x[1] = 2
    x[2] = 3

    print(x[0])
    print(x[1])
    print(x[2])

結果は以下の通り。

1.0
2.0
3.0

代入できてますね。


Tensor型を便利にするなんやかんや

配列とかを扱う上でやりたくなる操作をTensor型で実行してみます。

num_elements: 要素数の取得

Tensor型の要素数を取得するには、「num_elements」を使います。

num_elements(self: Self) -> Int

戻り値はInt型。
実際に使うコードは以下の通り。

from tensor import Tensor


fn main():
    var x = Tensor[DType.float32](3)
    print(x.num_elements())

これで要素数を取得できます。


おまけ: 要素を順番に取り出す

Pythonだとlist型等はそのままでも順番に取り出せますが、
MojoのTensor型は要素を順番に取り出す機能を持っていません。
なので、for文でインデックスを指定して順番に要素を取り出します。

from tensor import Tensor


fn main():
    var x = Tensor[DType.float32](3)
    var n = x.num_elements()
    for i in range(n):
        print(x[i])


type: 型の確認

宣言したTensor型が保持している型を確認するときは「type」を使います。

type(self: Self) -> DType

戻り値はDType型。
実際に使うコードは以下の通り。

from tensor import Tensor


fn main():
    var x = Tensor[DType.float32](3)
    print(x.type())

結果は以下の通り。

float32


astype: Tensorの要素の型変換

ここまでfloat32でTensor型を使ってますが、他の型に変換する時は
astype」を使います。

astype[new_dtype: DType](self: Self) -> Tensor[new_dtype]

戻り値はTensor型。
実際に使うコードは以下の通り。

from tensor import Tensor


fn main():
    var x = Tensor[DType.float32](3)
    var y = x.astype[DType.uint8]()
    print(y)

結果は以下の通り。

 Tensor([[0, 0, 0]], dtype=uint8, shape=3)

型変換できてますね。


clip: Tensorの値を上下限で切る

上下限を設定し、Tensor型の要素の値の中から上下限からはみ出した値は
自動的に上限または下限の値に置換される機能が「clip」です。

clip(self: Self, lower_bound: SIMD[dtype, 1], upper_bound: SIMD[dtype, 1]) -> Self

戻り値はTensor型。
実際に使うコードは以下の通り。

from tensor import Tensor


fn main():
    var x = Tensor[DType.float32](5)
    var lower = 3
    var upper = 6
    for i in range(x.num_elements()):
        x[i] = i ** 2

    var c = x.clip(lower, upper)
    print(x)
    print(c)

結果は以下の通り。

Tensor([[0.0, 1.0, 4.0, 9.0, 16.0]], dtype=float32, shape=5)
Tensor([[3.0, 3.0, 4.0, 6.0, 6.0]], dtype=float32, shape=5)

Tensorにはi^2の値を格納しています。
下限を3.0、上限は6.0に設定してxにclipを実行すると、
下限未満の値は下限値に、上限を超える値は上限値に置換されます。


data: ポインターを取得

Tensorが格納されているポインターを取得する時は「data」を使います。

data(self: Self) -> DTypePointer[dtype, 0]

戻り値はDTypePointer型。
実際に使うコードは以下の通り。

from tensor import Tensor


fn main():
    var x = Tensor[DType.float32](5)
    print(x.data())

これでポインターが返ってきます。


rank: Tensorの階数を取得

Tensorの階数を取得する時は「rank」を使います。

rank(self: Self) -> Int

戻り値はInt型。
実際に使うコードは以下の通り。

from tensor import Tensor


fn main():
    var x = Tensor[DType.float32](5)
    print(x.rank())


bytecount: Tensorのサイズを取得

Tensor型のデータサイズを取得するときは「bytecount」を使います。

bytecount(self: Self) -> Int

戻り値はInt型。
実際に使うコードは以下の通り。

from tensor import Tensor


fn main():
    var x = Tensor[DType.float32](5)
    var y = Tensor[DType.float32](5)
    var z = Tensor[DType.float32](100)
    var x_i = Tensor[DType.uint8](5)
    var y_i = Tensor[DType.uint8](100)
    print(x.bytecount())
    print(y.bytecount())
    print(z.bytecount())
    print(x_i.bytecount())
    print(y_i.bytecount())

結果は以下の通り。

20
20
400
5
100

サイズも型も同じxとyはサイズが同じになっています。
float32のTensorは初期状態で1要素あたり4Byte、
uint8のTensorは初期状態で1要素あたり1Byte使ってるみたいですね。


Tensorをもっと便利に扱いたい!

まだまだあります、Tensorの機能。
さらにインポートを増やしてできることも増やしましょう。


TensorSpec型

Tensor型の構造を保持する型です。
以下のインポートを追加します。

from tensor import TensorSpec

TensorSpecを使うと、Tensor型の宣言と具体的なTensorの構造を
分離して扱えるようになって便利ですね。

具体的には以下のように使用します。

from tensor import Tensor, TensorSpec


fn main():
    var width = 5
    var height = 5
    var dtype = DType.float32
    
    var spec = TensorSpec(dtype, height, width)
    var x = Tensor[DType.float32](spec)
    
    print(x)

結果は以下の通り。


Tensor([[0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0]], dtype=float32, shape=5x5)

5x5のTensorができました。
以下のようにすることで、カラー画像形式のTensorも作れます。

from tensor import Tensor, TensorSpec


fn main():
    var width = 5
    var height = 5
    var channel = 3
    var dtype = DType.uint8
    
    var spec = TensorSpec(dtype, height, width, channel)
    var x = Tensor[DType.uint8](spec)
    
    print(x)

TensorSpecに「高さx幅xチャンネル」を指定しました。
結果は以下の通り。

Tensor([[[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]],
[[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]],
[[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]],
[[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]],
[[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]],
dtype=uint8, shape=5x5x3)

見やすくするために少し整形しましたが、
5x5の各要素に3つの値を保持した配列がさらに入っていますね。


TensorShape型

Tensorの形状を保持する型です。
TensorShape型を使うには、以下のインポートを追加します。

from tensor import TensorShape

Tensorのリサイズの時に使ったりします。
TensorShapeの具体的な宣言方法は以下の通り。

from tensor import Tensor, TensorShape


fn main():
    var width = 5
    var height = 5
    var channel = 3
    
    var shape = TensorShape(height, width, channel)
    print(shape)

結果は以下の通り。

5x5x3

形状がTensorShape型で出力されました。


TensorSpecとTensorShapeで使える機能

ここまで解説した以下の機能はTensorSpecTensorShapeでも使えます。

  • rank

  • num_elements

TensorSpecのみ(TensorShapeでは使えない)機能には以下があります。

  • dtype

  • bytecount

実際に使ってみましょう。

from tensor import Tensor, TensorSpec, TensorShape


fn main():
    var width = 5
    var height = 5
    var channel = 3
    var dtype = DType.uint8
    var spec = TensorSpec(dtype, height, width, channel)
    var shape = TensorShape(height, width, channel)
    
    print('TensorSpec.rank = ' + str(spec.rank()))
    print('TensorSpec.num_elements = ' + str(spec.num_elements()))
    print('TensorSpec.dtype = ' + str(spec.dtype()))
    print('TensorSpec.bytecount = ' + str(spec.bytecount()) + '\n')
    
    print('TensorShape.rank = ' + str(shape.rank()))
    print('TensorShape.num_elements = ' + str(shape.num_elements()))

結果は以下の通り。

TensorSpec.rank = 3
TensorSpec.num_elements = 75
TensorSpec.dtype = uint8
TensorSpec.bytecount = 75

TensorShape.rank = 3
TensorShape.num_elements = 75

それぞれの具体的な動き方については変わりませんね。


Tensor + TensorSpec + TensorShape

さらに便利な機能がまだあるので、それらも見ていきましょう。


ここから使うTensorのテンプレート

以降はTensorの値を初期値ではなくちゃんと値を入れて扱います。
多分その方がわかりやすいので。
ただ、毎回ごちゃごちゃ中身を変えるとややこしいので、
決まった中身のTensorを先に決めておきます。

from tensor import Tensor, TensorSpec


fn main():
    var count = 225
    var dtype = DType.uint8
    var spec = TensorSpec(dtype, count)
    var template = Tensor[DType.uint8](spec)
    for i in range(template.num_elements()):
        template[i] = 255 - 17 * i

このTensorを使いますが、main関数が見にくくなるので外に出します。

from tensor import Tensor, TensorSpec


fn templates() -> Tensor[DType.uint8]:
    var count = 225
    var dtype = DType.uint8
    var spec = TensorSpec(dtype, count)
    var template = Tensor[DType.uint8](spec)
    for i in range(template.num_elements()):
        template[i] = 255 - 17 * i
    return template


fn main():
    var template = templates()
        print(template)

templates関数の戻り値が先ほど決めたTensorになっています。

結果は以下の通り。

Tensor([[255, 238, 221, ..., 65, 48, 31]], dtype=uint8, shape=225)

このTensorをベースとして、結果の変化に注目しましょう。

以降はこのtemplate関数を省略して書きますのでご注意ください。


ireshape: Tensorの形状変更

ireshape」を使うとTensorの形状を任意の形に変形することができます。
ireshapeの形状はTensorShape型を使用して指定します。

from tensor import Tensor, TensorSpec, TensorShape


fn main():
    var template = templates()
    var shape = TensorShape(15, 15)
    try:
        template.ireshape(shape)
    except:
        print('RESHAPE ERROR !!')
    print(template)

結果は以下の通り。

Tensor([[255, 238, 221, ..., 51, 34, 17],
[0, 239, 222, ..., 52, 35, 18],
[1, 240, 223, ..., 53, 36, 19],
..., 
[11, 250, 233, ..., 63, 46, 29],
[12, 251, 234, ..., 64, 47, 30],
[13, 252, 235, ..., 65, 48, 31]], dtype=uint8, shape=15x15)

Tensorが15x15に形状が変わってますね。
注意点として、指定した形状と元のTensorの要素数で整合性が取れなかった
場合にMojoはエラーを送出するので、tryで囲む必要があります。


reshape: 形状変更した新しいTensorを作成する

先ほどのireshapeは元のTensorを形状変更するため、
元のTensorを保持しない破壊的な操作ですとなります。
元のTensorを保持した上で新しい形状のTensorが欲しい場合は、
reshape」を使います。

from tensor import Tensor, TensorSpec, TensorShape


fn main():
    var template = templates()
    var shape = TensorShape(15, 15)
    try:
        var c = template.reshape(shape)
        print(c)
    except:
        print('RESHAPE ERROR !!')
    print(template)

結果は以下の通り。

Tensor([[255, 238, 221, ..., 51, 34, 17],
[0, 239, 222, ..., 52, 35, 18],
[1, 240, 223, ..., 53, 36, 19],
..., 
[11, 250, 233, ..., 63, 46, 29],
[12, 251, 234, ..., 64, 47, 30],
[13, 252, 235, ..., 65, 48, 31]], dtype=uint8, shape=15x15)
Tensor([[255, 238, 221, ..., 65, 48, 31]], dtype=uint8, shape=225)

元のTensorが保持された状態で新しい形状のTensorを生成できました。


spec: Tensorのspecを取得

TensorのspecをTensorSpec型で取得するのが「spec」です。

from tensor import Tensor, TensorSpec, TensorShape


fn main():
    var template = templates()
    var shape = TensorShape(15, 5, 3)
    
    try:
        template.ireshape(shape)
    except:
        print('RESHAPE ERROR !!')
        
    print(template.spec())

結果は以下の通り。

15x5x3xuint8

Tensorのspecが取得できました。
戻り値の型はTensorSpec型です。


shape: Tensorのshapeを取得

Tensorの形状をTensorShape型で取得するのが「shape」です。

from tensor import Tensor, TensorSpec, TensorShape


fn main():
    var template = templates()
    var shape = TensorShape(15, 5, 3)
    
    try:
        template.ireshape(shape)
    except:
        print('RESHAPE ERROR !!')
        
    print(template.shape())

結果は以下の通り。

15x5x3

Tensorの形状が取得できました。
戻り値はTensorShape型です。


dim: Tensorの次元を取得

Tensor内の特定のインデックスの次元を取得するには「dim」を使います。

from tensor import Tensor, TensorSpec, TensorShape


fn main():
    var template = templates()
    var shape = TensorShape(15, 5, 3)
    
    try:
        template.ireshape(shape)
    except:
        print('RESHAPE ERROR !!')
        
    print(template.dim(0))
    print(template.dim(1))
    print(template.dim(2))

結果は以下の通り。

15
5
3

TensorShapeのそれぞれの値を取得するには便利そうですね。
戻り値はInt型です。


おわりに

それなりに使う機能はひとまずまとめることができたかな〜と思います。
基本的に備忘録的な立ち位置なので、不備とかわけわからんことも書いてる可能性があります。
ご容赦願います。

この記事が気に入ったらサポートをしてみませんか?