Markov Logic Network (2)


[こちら](http://qiita.com/9_ties/items/3bdb177384937ddc88df)の続き。

## Weight Learning
前回は各論理式$f_i$に重み$w_i$が与えられているという状況でのクエリについてやったが、現実的には$w_i$は学習の対象となる。

重み$\mathbf{w}=(w_1,\ldots,w_m)$が与えられた時の確率分布は

```math
P(X=x|\mathbf{w})=\frac{1}{Z}\exp\left(\sum_iw_in_i(x)\right)\\
Z = \sum_x\exp\left(\sum_iw_in_i(x)\right)
```

だったので、対数尤度は

```math
\begin{align}
\log L(\mathbf{w}|X=x) &= \log P(X=x|\mathbf{w}) \\
                       &= \sum_iw_in_i(x) - \log Z
\end{align}
```

となる。これを最大化したいので勾配を計算すると

```math
\begin{align}
\frac{\partial}{\partial w_i}\log L(\mathbf{w}|X=x) &= n_i(x) - \sum_x n_i(x)\frac{1}{Z}\exp\left(\sum_iw_in_i(x)\right) \\
&= n_i(x) - \sum_x n_i(x) P(X=x|\mathbf{w}) \\
&= n_i(x) - E_x[n_i(x)]
\end{align}
```

となる。つまり、与えられた世界$x$で論理式$f_i$が成立している回数($n_i(x)$)が、期待される回数($E_x[n_i(x)]$)と等しくなる(つまり勾配が$0$となる)方向に$w_i$を進めれば良いという事。
ここで計算した勾配を使ってBFGS法などで尤度の最大化を行えば良い。再急降下法も試したが、収束速度が非常に遅かったのでBFGSもしくはL-BFGSなど二次の項まで見る方法を使う。

実際には$n_i(x)$を数えるのは大変だし、$E_x[n_i(x)]$は全ての可能な世界について和であるのでもっと困難であるので、モンテカルロ法等が必要となる。また、グラフで表現された条件付き独立性も活用出来る。これは後でやる。


## 計算例

前回と同じモデル

> $F_1: \neg\rm{Smokes}(x)\vee\rm{Cancer}(x): w_1$
> $F_2: \neg\rm{Friends}(x,y)\vee \neg\rm{Smokes}(x)\vee\rm{Smokes}(y): w_2$
> $F_3: \neg\rm{Friends}(x,y)\vee \rm{Smokes}(x)\vee\neg\rm{Smokes}(y): w_3$


で$C=\\{A,B\\}$かつ世界$x$が

$\rm{Smokes}(A)=True, \rm{Smokes}(B)=True$
$\rm{Friends}(A,A)=\rm{Friends}(B,B)=False, \rm{Friends}(A,B)=\rm{Friends}(B,A)=True$
$\rm{Cancer}(A)=True, \rm{Cancer}(B)=False$

の場合の$w_1,w_2,w_3$を、とりあえず定義通りに計算してみると以下のようになる。

```py
import pandas as pd
import numpy as np
from scipy.misc import logsumexp
from scipy.optimize import fmin_bfgs
from itertools import product

const = ['A', 'B']
preds = [('Smokes', 1), ('Cancer', 1), ('Friends', 2)]  # Predicate and arity

ground_atoms = [
    (p, *args)
    for p, arity in preds
    for args in product(const, repeat=arity)
    ]

print('=== Ground Atoms ===')
print(ground_atoms)

formulas = [
    # (atom, negation, arity, weight)
    ([('Smokes', (0,)), ('Cancer', (0,))], [1, 0], 1),
    ([('Friends', (0,1)), ('Smokes', (0,)), ('Smokes', (1,))], [1, 0, 1], 2),
    ([('Friends', (0,1)), ('Smokes', (0,)), ('Smokes', (1,))], [1, 1, 0], 2),
    ]

ground_formulas = []
for i, (clauses, neg, arity) in enumerate(formulas):
    for args in product(const, repeat=arity):
        ground_formula = [
            (p, *map(lambda i: args[i], v))
            for p, v in clauses
            ]
        ground_formulas.append((ground_formula, neg, i))

print('=== Ground Formulas ===')
print(ground_formulas)

# Generate all configurations
X = pd.DataFrame(columns=ground_atoms, data=list(product([1, 0], repeat=len(ground_atoms))))

def mloglike(w, world_idx):
    # Compute n_i(x) and sum_i(w_i*n_i(x))
    n = np.zeros((len(X), len(w)))
    S = np.zeros(len(X))
    for f, neg, i in ground_formulas:
        v = np.logical_xor(X[f], neg).any(1)
        S += w[i] * v
        n[:, i] += v

    # Compute joint probabilities
    logP = S - logsumexp(S)
    return -logP[world_idx]

def grad_mloglike(w, world_idx):
    # Compute n_i(x) and sum_i(w_i*n_i(x))
    n = np.zeros((len(X), len(w)))
    S = np.zeros(len(X))
    for f, neg, i in ground_formulas:
        v = np.logical_xor(X[f], neg).any(1)
        S += w[i] * v
        n[:, i] += v

    # Compute joint probabilities
    logP = S - logsumexp(S)

    # Compute gradient
    g = (n[world_idx] - np.exp(logsumexp(np.log(n) + logP.reshape(-1,1), axis=0)))
    return -g

print('=== Training ===')
world = [(('Smokes', 'A'), True),
         (('Smokes', 'B'), True),
         (('Friends', 'A', 'A'), False),
         (('Friends', 'B', 'B'), False),
         (('Friends', 'A', 'B'), True),
         (('Friends', 'B', 'A'), True),
         (('Cancer', 'A'), True),
         (('Cancer', 'B'), False)
         ]
world = np.array(world)
world_idx = np.where((X[world[:,0]] == world[:,1]).all(1))[0][0]

w0 = np.zeros(len(formulas))
w = fmin_bfgs(mloglike, w0, fprime=grad_mloglike, args=(world_idx,))
print(w)
```

結果は

```math
w_1 = -0.8958807, w_2=w_3=11.96577936
```

となる。