Named tensors

In pyAgrum>=2.0.0, Tensors (previously Potentials) represent multi-dimensionnal arrays with (discrete) random variables attached to each dimension. This mathematical object have tensorial operators w.r.t. to the variables attached.

Creative Commons License

aGrUM

interactive online version

In [1]:
import pyagrum as gum
import pyagrum.lib.notebook as gnb

va, vb, vc = [gum.LabelizedVariable(s, s, 2) for s in "abc"]

Tensor algebra

In [2]:
p1 = gum.Tensor(va, vb).fillWith([1, 2, 3, 4]).normalize()
p2 = gum.Tensor(vb, vc).fillWith([4, 5, 2, 3]).normalize()
In [3]:
gnb.flow.row(p1, p2, p1 + p2, captions=["p1", "p2", "p1+p2"])
a
b
0
1
0
0.10000.2000
1
0.30000.4000

p1
b
c
0
1
0
0.28570.3571
1
0.14290.2143

p2
b
a
c
0
1
0
0
0.38570.6571
1
0.24290.5143
1
0
0.48570.7571
1
0.34290.6143

p1+p2
In [4]:
p3 = p1 + p2
p3 / p3.sumOut(["b"])
Out[4]:
c
b
a
0
1
0
0
0.36990.3208
1
0.39080.3582
1
0
0.63010.6792
1
0.60920.6418
In [5]:
p4 = gum.Tensor() + p3
gnb.flow.row(p3, p4, captions=["p3", "p4"])
b
a
c
0
1
0
0
0.38570.6571
1
0.24290.5143
1
0
0.48570.7571
1
0.34290.6143

p3
b
a
c
0
1
0
0
1.38571.6571
1
1.24291.5143
1
0
1.48571.7571
1
1.34291.6143

p4

Bayes’ theorem

In [6]:
bn = gum.fastBN("a->c;b->c", 3)
bn
Out[6]:
G c c b b b->c a a a->c

In such a small bayes net, we can directly manipulate \(P(a,b,c)\). For instance :

\[P(b|c)=\frac{\sum_{a} P(a,b,c)}{\sum_{a,b} P(a,b,c)}\]
In [7]:
pABC = bn.cpt("a") * bn.cpt("b") * bn.cpt("c")
pBgivenC = pABC.sumOut(["a"]) / pABC.sumOut(["a", "b"])

pBgivenC.putFirst("b")  # in order to have b horizontally in the table
Out[7]:
b
c
0
1
2
0
0.46670.50240.0308
1
0.48850.46440.0471
2
0.65210.27610.0719

Joint, marginal probability, likelihood

Let’s compute the joint probability \(P(A,B)\) from \(P(A,B,C)\)

In [8]:
pAC = pABC.sumOut(["b"])
print("pAC really is a probability : it sums to {}".format(pAC.sum()))
pAC
pAC really is a probability : it sums to 0.9999999999999999
Out[8]:
a
c
0
1
2
0
0.26080.17820.0036
1
0.22110.07150.0051
2
0.11470.14090.0041

Computing \(p(A)\)

In [9]:
pAC.sumOut(["c"])
Out[9]:
a
0
1
2
0.59670.39060.0128

Computing \(p(A |C=1)\)

It is easy to compute \(p(A, C=1)\)

In [10]:
pAC.extract({"c": 1})
Out[10]:
a
0
1
2
0.22110.07150.0051

Moreover, we know that \(P(C=1)=\sum_A P(A,C=1)\)

In [11]:
pAC.extract({"c": 1}).sum()
Out[11]:
0.2977224595357324

Now we can compute \(p(A|C=1)=\frac{P(A,C=1)}{p(C=1)}\)

In [12]:
pAC.extract({"c": 1}).normalize()
Out[12]:
a
0
1
2
0.74270.24030.0170

Computing \(P(A|C)\)

\(P(A|C)\) is represented by a matrix that verifies \(p(A|C)=\frac{P(A,C)}{P(C}\)

In [13]:
pAgivenC = (pAC / pAC.sumIn("c")).putFirst("a")
# putFirst("a") : to correctly show a cpt, the first variable have to bethe conditionned one
gnb.flow.row(pAgivenC, pAgivenC.extract({"c": 1}), captions=["$P(A|C)$", "$P(A|C=1)$"])
a
c
0
1
2
0
0.58930.40250.0082
1
0.74270.24030.0170
2
0.44180.54260.0157

$P(A|C)$
a
0
1
2
0.74270.24030.0170

$P(A|C=1)$

Likelihood \(P(A=2|C)\)

A likelihood can also be found in this matrix.

In [14]:
pAgivenC.extract({"a": 2})
Out[14]:
c
0
1
2
0.00820.01700.0157

A likelihood does not have to sum to 1. It is not relevant to normalize it.

In [15]:
pAgivenC.sumIn(["a"])
Out[15]:
a
0
1
2
1.77381.18530.0409

entropy of (probabilistic) tensor

In [16]:
%matplotlib inline
from pylab import *
import matplotlib.pyplot as plt
import numpy as np
In [17]:
p1 = gum.Tensor(va)
x = np.linspace(0, 1, 100)
plt.plot(x, [p1.fillWith([p, 1 - p]).entropy() for p in x])
plt.show()
../_images/notebooks_93-Tools_tensors_31_0.svg
In [18]:
t = gum.LabelizedVariable("t", "t", 3)
p1 = gum.Tensor().add(t)


def entrop(bc):
  """
  bc is a list [a,b,c] close to a distribution
  (normalized just to be sure)
  """
  return p1.fillWith(bc).normalize().entropy()


import matplotlib.tri as tri

corners = np.array([[0, 0], [1, 0], [0.5, 0.75**0.5]])
triangle = tri.Triangulation(corners[:, 0], corners[:, 1])

# Mid-points of triangle sides opposite of each corner
midpoints = [(corners[(i + 1) % 3] + corners[(i + 2) % 3]) / 2.0 for i in range(3)]


def xy2bc(xy, tol=1.0e-3):
  """
  From 2D Cartesian coordinates to barycentric.
  """
  s = [(corners[i] - midpoints[i]).dot(xy - midpoints[i]) / 0.75 for i in range(3)]
  return np.clip(s, tol, 1.0 - tol)


def draw_entropy(nlevels=200, subdiv=6, **kwargs):
  refiner = tri.UniformTriRefiner(triangle)
  trimesh = refiner.refine_triangulation(subdiv=subdiv)
  pvals = [entrop(xy2bc(xy)) for xy in zip(trimesh.x, trimesh.y)]

  plt.tricontourf(trimesh, pvals, nlevels, **kwargs)
  plt.axis("equal")
  plt.ylim(0, 0.75**0.5)
  plt.axis("off")


draw_entropy()
plt.show()
../_images/notebooks_93-Tools_tensors_32_0.svg
In [ ]: