FXTensor
Numpy を利用してマルコフ・テンソルを効率的に計算するための FXTensor クラスです。
インストール
pip install fxtensor-salmon
このクラスは、確率的な事象の連鎖をテンソルとして表現し、それらの結合や周辺化といった操作を可能にします。これにより、複雑な確率モデルを体系的に扱うことができます。
テンソル計算
- 結合演算 (
composition): 2つのテンソルを結合します(行列積の一般化)。 - テンソル積 (
tensor_product): 2つのテンソルを並列に組み合わせます。 - 部分結合 (
partial_composition): テンソルの一部だけを結合します。 - 同時化 (
jointification): 2つの状態から同時確率分布を生成します。 - 条件化 (
conditionalization): 同時確率分布から条件付き確率分布を求めます。 - 第一・第二周辺化 (
first_marginalization,second_marginalization): 特定の確率変数を消去(積分)します。
テンソル生成(クラスメソッド)
- 恒等射/単位テンソル (
unit_tensor): 何も変化させないテンソルを作成します。 - マルコフ・テンソル Δ (
delta): 状態を複製(コピー)するテンソルを作成します。 - マルコフ・テンソル ! (
exclamation): 状態を破棄(無視)するテンソルを作成します。 - マルコフ・テンソル Xa,b (スワップ) (
swap): 2つの状態を入れ替えるテンソルを作成します。
はじめに
本スクリプトにおける計算の基本
次のような行と列にインデックスを持つ確率の表(行列)を考えます。各列の合計は1になるものとします。これは、ある列のインデックスで表現される事象が起きたという条件下で、ある行のインデックスで表現される事象が起きる条件付き確率を表します。
例として、次のような表1と表2を示します。
表1. 黒、または白のボールが混在した容器からボールを1個選んだ後、その色に応じて赤、緑、青のランプがランダムに発光する確率。
黒 白
-----------
赤 0.1 0.2
緑 0.2 0.3
青 0.7 0.5
表2. 赤、緑、青のランプが発光した際に、別の白色ランプが光るかどうかの確率。
赤 緑 青
-----------------------------------------
白色のランプが光る 0.8 0.9 0.9
白色のランプが光らない 0.2 0.1 0.1
ここで、表1と表2の結合を考えます。これは、ボールを選んでから最終的に白色ランプがどうなるかまでの一連の確率を計算することに対応します。
例えば、「黒ボールを選び」→「赤いランプが光り (確率0.1)」→「その結果、白色ランプが光る (確率0.8)」という一連の事象が起きる確率は 0.1 * 0.8 = 0.08 です。
同様に、ランプが緑、青の場合も計算し、全て足し合わせると、「黒ボールを選んだ場合に、最終的に白色ランプが光る確率」が求まります。
(0.1 * 0.8) + (0.2 * 0.9) + (0.7 * 0.9) = 0.08 + 0.18 + 0.63 = 0.89
この計算をすべての組み合わせで行うと、次の表3が得られます。
表3. ボールの色を条件として、最終的に白色ランプが光るかどうかの確率。
黒 白
-----------------------------------------
白色のランプが光る 0.89 0.88 (22/25)
白色のランプが光らない 0.11 0.12 (3/25)
本スクリプト FXTensor クラスを用いると、この計算は composition メソッドで直感的に実行できます。
import numpy as np
from fxtensor_salmon import FXTensor # FXTensor クラスをインポート
# -- ラベルとインデックスの対応 --
# 状態空間の定義
ball_domain = ['黒', '白']
lamp_colors = ['赤', '緑', '青']
white_lamp_codomain = ['白色のランプが光る', '白色のランプが光らない']
def main():
# --- 表1をFXTensorで表現 ---
# profileは[入力の次元], [出力の次元]
# 入力: ボールの色 (2種類)、出力: ランプの色 (3種類)
profile_a = [[len(ball_domain)], [len(lamp_colors)]]
# データはNumpy配列で表現。P(出力|入力)の形
# data[入力インデックス, 出力インデックス]
data_a = np.array([
# 入力が'黒' (インデックス0) の場合
[0.1, 0.2, 0.7], # 出力: [P(赤|黒), P(緑|黒), P(青|黒)]
# 入力が'白' (インデックス1) の場合
[0.2, 0.3, 0.5] # 出力: [P(赤|白), P(緑|白), P(青|白)]
])
tensor_a = FXTensor(profile_a, data=data_a)
# --- 表2をFXTensorで表現 ---
# 入力: ランプの色 (3種類)、出力: 白色ランプの状態 (2種類)
profile_b = [[len(lamp_colors)], [len(white_lamp_codomain)]]
data_b = np.array([
# 入力が'赤' (インデックス0) の場合
[0.8, 0.2], # 出力: [P(光る|赤), P(光らない|赤)]
# 入力が'緑' (インデックス1) の場合
[0.9, 0.1], # 出力: [P(光る|緑), P(光らない|緑)]
# 入力が'青' (インデックス2) の場合
[0.9, 0.1] # 出力: [P(光る|青), P(光らない|青)]
])
tensor_b = FXTensor(profile_b, data=data_b)
# --- テンソルの結合 ---
# tensor_a の出力と tensor_b の入力が接続される
result_tensor = tensor_a.composition(tensor_b)
# --- 結果の表示 ---
print("結合後のプロファイル:", result_tensor.profile)
print("結合後のデータ (Numpy配列):\n", result_tensor.data)
print("\n--- 結果の解釈 ---")
for i, ball_color in enumerate(ball_domain):
for j, lamp_status in enumerate(white_lamp_codomain):
prob = result_tensor.data[i, j]
print(f"P({lamp_status} | {ball_color}) = {prob:.2f}")
if __name__ == "__main__":
main()
上記のコードを実行すると、composition によって2つの確率過程が結合され、次のような実行結果を得ます。
結合後のプロファイル: [[2], [2]]
結合後のデータ (Numpy配列):
[[0.89 0.11]
[0.88 0.12]]
--- 結果の解釈 ---
P(白色のランプが光る | 黒) = 0.89
P(白色のランプが光らない | 黒) = 0.11
P(白色のランプが光る | 白) = 0.88
P(白色のランプが光らない | 白) = 0.12
行と列のラベルがそれぞれ複数ある場合
この考え方は、入出力が複数の要素の組(タプル)で表される、より高次元のテンソルにも拡張できます。
例えば、「(ボールの色, カードの色)」という入力に対して、「(ランプ1の色, ランプ2の色)」という出力が得られるような、より複雑な確率過程も同様に扱うことができます。この場合、データは2次元の行列ではなく、4次元以上の numpy 配列で表現されます。composition などの演算は、これらの高次元配列に対しても同様に機能します。
以下の表4、5、6はそのような例です。
表4. (ボール, カード) → (ランプ色1, ランプ色2) の確率
(黒, 金) (黒, 銀)(白, 金)(白, 銀)
--------------------------------------------
(赤, 桃) 0.1 0.1 0.1 0.1
(赤, 紫) 0.1 0.1 0.1 0.1
(緑, 桃) 0.1 0.1 0.1 0.5
(緑, 紫) 0.1 0.1 0.5 0.1
(青, 桃) 0.1 0.5 0.1 0.1
(青, 紫) 0.5 0.1 0.1 0.1
--------------------------------------------
表5. (ランプ色1, ランプ色2) → (白色ランプ1の状態, 白色ランプ2の状態) の確率
(赤, 桃) (赤, 紫)(緑, 桃) (緑, 紫)(青, 桃) (青, 紫)
-----------------------------------------------------------------------------------------------------------
(白色のランプ 1 が光る, 白色のランプ 2 が光る) 0.1 0.4 0.3 0.2 0.1 0.2
(白色のランプ 1 が光る, 白色のランプ 2 が光らない) 0.2 0.1 0.4 0.3 0.1 0.6
(白色のランプ 1 が光らない, 白色のランプ 2 が光る) 0.3 0.2 0.1 0.4 0.2 0.1
(白色のランプ 1 が光らない, 白色のランプ 2 が光らない) 0.4 0.3 0.2 0.1 0.6 0.1
-----------------------------------------------------------------------------------------------------------
これらのテンソルを FXTensor クラスで表現し、composition を用いて結合することで、最終的な結果である表6を導出できます。
表6. (ボール, カード) → (白色ランプ1の状態, 白色ランプ2の状態) の確率
(黒, 金) (黒, 銀)(白, 金)(白, 銀)
------------------------------------------------------------------------------------------------
(白色のランプ 1 が光る, 白色のランプ 2 が光る) 21/100 17/100 21/100 1/4
(白色のランプ 1 が光る, 白色のランプ 2 が光らない) 41/100 21/100 29/100 33/100
(白色のランプ 1 が光らない, 白色のランプ 2 が光る) 17/100 21/100 29/100 17/100
(白色のランプ 1 が光らない, 白色のランプ 2 が光らない) 21/100 41/100 21/100 1/4
------------------------------------------------------------------------------------------------
この計算は、FXTensor の profile に [[2, 2], [3, 2]] のように複数の次元を指定し、data に4階のテンソル(4次元のNumpy配列)を渡すことで実現されます。
JSON ファイルの入出力例
import numpy as np
from fxtensor import FXTensor
json_data = {
"profile": [[2], [2]],
"strands": [
{"from": [0], "to": [0], "weight": 0.3},
{"from": [0], "to": [1], "weight": 0.7},
{"from": [1], "to": [0], "weight": 0.5},
{"from": [1], "to": [1], "weight": 0.5}
]
}
# JSONデータからテンソルを作成
stochastic_map = FXTensor.from_json(json_data)
print(stochastic_map)
# 出力予測: FXTensor(profile=[[2], [2]], shape=(2, 2))
print(stochastic_map.data)
# 出力予測:
# [[0.3 0.7]
# [0.5 0.5]]
参考文献
- 檜山正幸のキマイラ飼育記 (はてなBlog), マルコフ圏 A First Look -- 圏論的確率論の最良の定式化, https://m-hiyama.hatenablog.com/entry/2020/06/09/154044
- 檜山正幸のキマイラ飼育記 (はてなBlog), マルコフ圏におけるテンソル計算の手順とコツ, https://m-hiyama.hatenablog.com/entry/2021/04/05/153325
JSON定義から
FXTensorオブジェクトを作成する方法は以下の通りです。
Metadata
Release files for fxtensor_salmon 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| fxtensor_salmon-0.1.1.tar.gz | 13.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fxtensor_salmon-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 22.6 kB
Release files / fxtensor_salmon-0.1.1.tar.gz
| Download URL | fxtensor_salmon-0.1.1.tar.gz |
|---|---|
| Size | 13.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
47789eacf78d19631f181436cd494a4c0c3729608286310aa3804df6625390f2
|
|
BLAKE2b-256 checksum How to use checksums |
775ed48b18552617d3c8dfafea683d58882afde48f3e793805c8a6668bf49ba3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.3
|
Release files / fxtensor_salmon-0.1.1-py3-none-any.whl
| Download URL | fxtensor_salmon-0.1.1-py3-none-any.whl |
|---|---|
| Size | 9.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
41c8ecb2ae809516928ce55486754b0a5be8402c9806f66d9c2168d9e1285739
|
|
BLAKE2b-256 checksum How to use checksums |
ba3e4f1e1f59f3361677ef49b474933335122280eadd1a6afaf7571544a2d385
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.13.3
|