PyTorchに入門しよう① Tensorの使い方

## はじめに
今更になってAIの勉強を始めたいと思ったため、PyTorchのチュートリアルを読んで学習をしました。
この記事では、PyTorchのチュートリアルの内容を日本語に翻訳して、一部情報を追記しながらまとめていきます。

私の実行環境は下記のとおりです。
`Debian 13`
`Python 3.11.9`
`PyTorch 2.4.1`


## Tensorとは
Tensorとは、配列や行列によく似たPyTorch独自のデータ構造です。
PyTorchではモデルへの入力や出力、パラメータの調整にこれを使います。
Tensorの利点として以下が挙げられます。
- GPUやハードウェアアクセラレータ上に配置可能
- 学習に必要な自動微分に最適化されている


## Tensorの生成方法
必要なライブラリのインポート
```Python
import torch
import numpy as np
```

### データから直接生成する
tensor()を使うことで、リストや数値から直接Tensorを生成することができます。
```Python
data = [[1,2],[3,4]]
x_data = torch.tensor(data) # Tensor([[1,2],[3,4]])
x_num  = torch.tensor(1.)   # Tensor(1.)
```

### numpy配列から生成する
numpy配列からもTensorは作成することができます。

:::note warn
numpy配列とTensorはメモリを共有しているため、どちらか一方を変更するともう一方も変更されます
::: 
```Python
np_arr = np.array(data)
x_np   = tensor.from_numpy(np_arr)
x_np[0][1] = 100
print(np_arr)  # array([[1,100],[3,4]])
print(x_np)    # tensor([[1,100],[3,4]])
```

### その他の生成方法
shapeを指定して、要素がランダムや1のTensorを作成することもできます。
shapeを指定する以外では、Tensorを引数に渡すことでそのshapeをコピーすることができます。
```Python
x_ones = torch.ones_like(x_data)                     # tensor([[1,1],[1,1]])
x_rand = torch.rand_like(x_data, dtype=torch.float)  # tensor([[0.30,0.30],[0.87,0.50]])

shape = (2,3,)
rand_tensor  = torch.rand(shape)  # tensor([[0.81,0.39,0.04],[0.11,0.57,0.74]])
ones_tensor  = torch.ones(shape)  # tensor([[1,1,1],[1,1,1]])
zeros_tensor = torch.zeros(shape) # tensor([[0,0,0],[0,0,0]])
```


## Tensorが持つ属性
作成したTensorは以下の3つの属性をインスタンス変数として持っています。
<dl>
<dt>shape</dt>
<dd>作成したTensorの形状。reshape()を使うことで形状を変更することが可能。</dd>
<dt>dtype</dt>
<dd>TensorのDataType。作成する際に指定をしなければ、データから推論される。</dd>
<dt>device</dt>
<dd>Tensorが配置されるデバイス。デフォルトではcpu上に配置される。</dd>
</dl>

TensorをGPU上に配置するには、以下のコードを実行する。
このコードでは、まずcudaが有効化されているかを確認し、有効化されている場合は``to()``メソッドを使用してTensorをGPU上にコピーする。
ここでTensorは元の値がコピーされるため、numpy配列とのメモリの共有はされなくなる。
```Python
tensor = torch.rand(4)
if torch.cuda.is_available():
    tensor = tensor.to("cuda")
```


## Tensorに対する演算
Tensorに対する演算は100以上定義されているため、ここではよく利用されるものを抜粋します。
詳しくは[こちら](https://pytorch.org/docs/stable/torch.html)を参照してください。

### 行、カラムの抽出、Tensorの結合
PyTorchではnumpyと似た方法で行やカラムの抽出を行うことができる。
以下にコード例を記載する。
```Python
tensor = torch.ones(4,4)
# 1行目の抽出
tensor[0]
# 1列目の抽出
tensor[:,0]
# 最後尾のカラムの抽出
tensor[...,-1]
# Tensorの結合
torch.cat([tensor,tensor], dim=1) # dim=0で縦に結合、dim=1で横に結合する
```

### 算術演算
転置行列は``tensor.T``とすることで求まります。
```Python
tensor = torch.rand(2,2)
transpose = tensor.T
```

次のコードでは行列の積を求めています。
行列の積の求め方はいくつかあり、下のy1~y3はどれも同じ結果です。
```Python
y1 = tensor @ tensor.T
y2 = tensor.matmul(tensor.T)
y3 = torch.zeros_like(y1)  # 出力に使用する変数の定義
torch.matmul(tensor, tensor.T, out=y3)
```

次のコードでは成分ごとの積を求めています。
下のz1~z3はどれも同じ結果になります。
numpy同様、スカラー値やshapeの違うTensorも上手く変形して計算してくれます。
```Python
z1 = tensor * tensor
z2 = tensor.mul(tensor)
z3 = torch.zeros_like(tensor)
torch.mul(tensor, tensor, out=z3)
```

### プリミティブ型への変換
Tensorを集約するなどして単一要素に形状を変化させたとき、``item()``メソッドを使用することでpythonのプリミティブ型に変換することができます。
```Python
agg = tensor.sum()
agg_item = agg.item()
type(agg_item)   # <class 'float'>
```

### 破壊的な演算
PyTorchでは演算結果を新しいTensorで返す、非破壊的な演算が主ですが、
メソッドの末尾に``_``を付けることで、Tensorの値を変化させる破壊的な演算を行うことができます。
以下では各要素に5を加える``sum``メソッドを破壊的な演算に変更しています。
```Python
tensor.add_(5)
```


## Numpyとの相互変換
TensorとNumpyは下記のようにして相互に変換することができます。
```Python
# Numpy -> Tensor
np_arr = numpy.array([1,2,3,4])
tensor = torch.from_numpy(np_arr)
# Tensor -> Numpy
np_arr = tensor.numpy()
```


## 最後に
稚拙な説明でしたが、これでTensorについての説明は終わりです。
次はモデルの学習に使用するDataLoaderとDatasetについて解説していきます。
いいねやご意見をぜひお願いします。

## 次の記事
https://qiita.com/175Rin/items/e60cb95b79dcfdd61963