I have implemented a Gibbs sampler to generate textured images. According to the beta
parameters (array of shape(4)), we can generate various textures.
Here is my initial function using Numpy:
def gibbs_sampler(img_label, betas, burnin, nb_samples):
nb_iter = burnin + nb_samples
lst_samples = []
labels = np.unique(img)
M, N = img.shape
img_flat = img.flatten()
# build neighborhood array by means of numpy broadcasting:
m, n = np.ogrid[0:M, 0:N]
top_left, top, top_right = m[0:-2, :]*N + n[:, 0:-2], m[0:-2, :]*N + n[:, 1:-1] , m[0:-2, :]*N + n[:, 2:]
left, pix, right = m[1:-1, :]*N + n[:, 0:-2], m[1:-1, :]*N + n[:, 1:-1], m[1:-1, :]*N + n[:, 2:]
bottom_left, bottom, bottom_right = m[2:, :]*N + n[:, 0:-2], m[2:, :]*N + n[:, 1:-1], m[2:, :]*N + n[:, 2:]
mat_neigh = np.dstack([pix, top, bottom, left, right, top_left, bottom_right, bottom_left, top_right])
mat_neigh = mat_neigh.reshape((-1, 9))
ind = np.arange((M-2)*(N-2))
# loop over iterations
for iteration in np.arange(nb_iter):
np.random.shuffle(ind)
# loop over pixels
for i in ind:
truc = map(functools.partial(lambda label, img_flat, mat_neigh : 1-np.equal(label, img_flat[mat_neigh[i, 1:]]).astype(np.uint), img_flat=img_flat, mat_neigh=mat_neigh), labels)
# bidule is of shape (4, 2, labels.size)
bidule = np.array(truc).T.reshape((-1, 2, labels.size))
# theta is of shape (labels.size, 4)
theta = np.sum(bidule, axis=1).T
# prior is thus an array of shape (labels.size)
prior = np.exp(-np.dot(theta, betas))
# sample from the posterior
drawn_label = np.random.choice(labels, p=prior/np.sum(prior))
img_flat[(i//(N-2) + 1)*N + i%(N-2) + 1] = drawn_label
if iteration >= burnin:
print('Iteration %i --> sample' % iteration)
lst_samples.append(copy.copy(img_flat.reshape(M, N)))
else:
print('Iteration %i --> burnin' % iteration)
return lst_samples
We cannot get rid of any loop as it is an iterative algorithm. I have thus tried to speed it up by using Cython (with static typing):
from __future__ import division
import numpy as np
import copy
cimport numpy as np
import functools
cimport cython
INTTYPE = np.int
DOUBLETYPE = np.double
ctypedef np.int_t INTTYPE_t
ctypedef np.double_t DOUBLETYPE_t
@cython.boundscheck(False)
@cython.wraparound(False)
@cython.nonecheck(False)
def func_for_map(label, img_flat, mat_neigh, i):
return (1-np.equal(label, img_flat[mat_neigh[i, 1:]])).astype(INTTYPE)
def gibbs_sampler(np.ndarray[INTTYPE_t, ndim=2] img_label, np.ndarray[DOUBLETYPE_t, ndim=1] betas, INTTYPE_t burnin=5, INTTYPE_t nb_samples=1):
assert img_label.dtype == INTTYPE and betas.dtype== DOUBLETYPE
cdef unsigned int nb_iter = burnin + nb_samples
lst_samples = list()
cdef np.ndarray[INTTYPE_t, ndim=1] labels
labels = np.unique(img_label)
cdef unsigned int M, N
M = img_label.shape[0]
N = img_label.shape[1]
cdef np.ndarray[INTTYPE_t, ndim=1] ind
ind = np.arange((M-2)*(N-2), dtype=INTTYPE)
cdef np.ndarray[INTTYPE_t, ndim=1] img_flat
img_flat = img_label.flatten()
# build neighborhood array:
cdef np.ndarray[INTTYPE_t, ndim=2] m
cdef np.ndarray[INTTYPE_t, ndim=2] n
m = (np.ogrid[0:M, 0:N][0]).astype(INTTYPE)
n = (np.ogrid[0:M, 0:N][1]).astype(INTTYPE)
cdef np.ndarray[INTTYPE_t, ndim=2] top_left, top, top_right, left, pix, right, bottom_left, bottom, bottom_right
top_left, top, top_right = m[0:-2, :]*N + n[:, 0:-2], m[0:-2, :]*N + n[:, 1:-1] , m[0:-2, :]*N + n[:, 2:]
left, pix, right = m[1:-1, :]*N + n[:, 0:-2], m[1:-1, :]*N + n[:, 1:-1], m[1:-1, :]*N + n[:, 2:]
bottom_left, bottom, bottom_right = m[2:, :]*N + n[:, 0:-2], m[2:, :]*N + n[:, 1:-1], m[2:, :]*N + n[:, 2:]
cdef np.ndarray[INTTYPE_t, ndim=3] mat_neigh_init
mat_neigh_init = np.dstack([pix, top, bottom, left, right, top_left, bottom_right, bottom_left, top_right])
cdef np.ndarray[INTTYPE_t, ndim=2] mat_neigh
mat_neigh = mat_neigh_init.reshape((-1, 9))
cdef unsigned int i
truc = list()
cdef np.ndarray[INTTYPE_t, ndim=3] bidule
cdef np.ndarray[INTTYPE_t, ndim=2] theta
cdef np.ndarray[DOUBLETYPE_t, ndim=1] prior
cdef unsigned int drawn_label, iteration
# loop over ICE iterations
for iteration in np.arange(nb_iter):
np.random.shuffle(ind)
# loop over pixels
for i in ind:
truc = map(functools.partial(func_for_map, img_flat=img_flat, mat_neigh=mat_neigh, i=i), labels)
bidule = np.array(truc).T.reshape((-1, 2, labels.size)).astype(INTTYPE)
theta = np.sum(bidule, axis=1).T
# ok so far
prior = np.exp(-np.dot(theta, betas)).astype(DOUBLETYPE)
# print('ok after prior')
# return 0
# sample from the posterior
drawn_label = np.random.choice(labels, p=prior/np.sum(prior))
img_flat[(i//(N-2) + 1)*N + i%(N-2) + 1] = drawn_label
if iteration >= burnin:
print('Iteration %i --> sample' % iteration)
lst_samples.append(copy.copy(img_flat.reshape(M, N)))
else:
print('Iteration %i --> burnin' % iteration)
return lst_samples
However, I ended up with the almost the same computation time, the numpy version being slightly faster than the Cython one.
I am thus trying to improve the Cython code.
EDIT:
For both functions (Cython and no Cython): I have replaced:
truc = map(functools.partial(lambda label, img_flat, mat_neigh : 1-np.equal(label, img_flat[mat_neigh[i, 1:]]).astype(np.uint), img_flat=img_flat, mat_neigh=mat_neigh), labels)
by a broadcasting:
truc = 1-np.equal(labels[:, None], img_flat[mat_neigh[i, 1:]][None, :])
all np.arange
by range
, and the computation of prior is now done by means of np.einsum
as suggested by Divakar.
Both functions are faster than previously, but the Python one is still slightly faster than the Cython one.