You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

randomWalkKernel.py 38 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834
  1. """
  2. @author: linlin
  3. @references: S Vichy N Vishwanathan, Nicol N Schraudolph, Risi Kondor, and Karsten M Borgwardt. Graph kernels. Journal of Machine Learning Research, 11(Apr):1201–1242, 2010.
  4. """
  5. import sys
  6. sys.path.insert(0, "../")
  7. import time
  8. from functools import partial
  9. from tqdm import tqdm
  10. import networkx as nx
  11. import numpy as np
  12. from scipy.sparse import identity, kron
  13. from scipy.sparse.linalg import cg
  14. from scipy.optimize import fixed_point
  15. from pygraph.utils.graphdataset import get_dataset_attributes
  16. from pygraph.utils.parallel import parallel_gm
  17. def randomwalkkernel(*args,
  18. # params for all method.
  19. compute_method=None,
  20. weight=1,
  21. p=None,
  22. q=None,
  23. edge_weight=None,
  24. # params for conjugate and fp method.
  25. node_kernels=None,
  26. edge_kernels=None,
  27. node_label='atom',
  28. edge_label='bond_type',
  29. # params for spectral method.
  30. sub_kernel=None,
  31. n_jobs=None):
  32. """Calculate random walk graph kernels.
  33. Parameters
  34. ----------
  35. Gn : List of NetworkX graph
  36. List of graphs between which the kernels are calculated.
  37. /
  38. G1, G2 : NetworkX graphs
  39. 2 graphs between which the kernel is calculated.
  40. node_label : string
  41. node attribute used as label. The default node label is atom.
  42. edge_label : string
  43. edge attribute used as label. The default edge label is bond_type.
  44. h : integer
  45. Longest length of walks.
  46. method : string
  47. Method used to compute the random walk kernel. Available methods are 'sylvester', 'conjugate', 'fp', 'spectral' and 'kron'.
  48. Return
  49. ------
  50. Kmatrix : Numpy matrix
  51. Kernel matrix, each element of which is the path kernel up to d between 2 praphs.
  52. """
  53. compute_method = compute_method.lower()
  54. Gn = args[0] if len(args) == 1 else [args[0], args[1]]
  55. eweight = None
  56. if edge_weight == None:
  57. print('\n None edge weight specified. Set all weight to 1.\n')
  58. else:
  59. try:
  60. some_weight = list(
  61. nx.get_edge_attributes(Gn[0], edge_weight).values())[0]
  62. if isinstance(some_weight, float) or isinstance(some_weight, int):
  63. eweight = edge_weight
  64. else:
  65. print(
  66. '\n Edge weight with name %s is not float or integer. Set all weight to 1.\n'
  67. % edge_weight)
  68. except:
  69. print(
  70. '\n Edge weight with name "%s" is not found in the edge attributes. Set all weight to 1.\n'
  71. % edge_weight)
  72. ds_attrs = get_dataset_attributes(
  73. Gn,
  74. attr_names=['node_labeled', 'node_attr_dim', 'edge_labeled',
  75. 'edge_attr_dim', 'is_directed'],
  76. node_label=node_label,
  77. edge_label=edge_label)
  78. # remove graphs with no edges, as no walk can be found in their structures,
  79. # so the weight matrix between such a graph and itself might be zero.
  80. len_gn = len(Gn)
  81. Gn = [(idx, G) for idx, G in enumerate(Gn) if nx.number_of_edges(G) != 0]
  82. idx = [G[0] for G in Gn]
  83. Gn = [G[1] for G in Gn]
  84. if len(Gn) != len_gn:
  85. print('\n %d graphs are removed as they don\'t contain edges.\n' %
  86. (len_gn - len(Gn)))
  87. start_time = time.time()
  88. # # get vertex and edge concatenated labels for each graph
  89. # label_list, d = getLabels(Gn, node_label, edge_label, ds_attrs['is_directed'])
  90. # gmf = filterGramMatrix(A_wave_list[0], label_list[0], ('C', '0', 'O'), ds_attrs['is_directed'])
  91. if compute_method == 'sylvester':
  92. import warnings
  93. warnings.warn('All labels are ignored.')
  94. Kmatrix = _sylvester_equation(Gn, weight, p, q, eweight, n_jobs)
  95. elif compute_method == 'conjugate':
  96. Kmatrix = _conjugate_gradient(Gn, weight, p, q, ds_attrs,
  97. node_kernels, edge_kernels,
  98. node_label, edge_label, eweight, n_jobs)
  99. elif compute_method == 'fp':
  100. Kmatrix = _fixed_point(Gn, weight, p, q, ds_attrs, node_kernels,
  101. edge_kernels, node_label, edge_label,
  102. eweight, n_jobs)
  103. elif compute_method == 'spectral':
  104. import warnings
  105. warnings.warn('All labels are ignored. Only works for undirected graphs.')
  106. Kmatrix = _spectral_decomposition(Gn, weight, p, q, sub_kernel, eweight, n_jobs)
  107. elif compute_method == 'kron':
  108. for i in range(0, len(Gn)):
  109. for j in range(i, len(Gn)):
  110. Kmatrix[i][j] = _randomwalkkernel_kron(Gn[i], Gn[j],
  111. node_label, edge_label)
  112. Kmatrix[j][i] = Kmatrix[i][j]
  113. else:
  114. raise Exception(
  115. 'compute method name incorrect. Available methods: "sylvester", "conjugate", "fp", "spectral" and "kron".'
  116. )
  117. run_time = time.time() - start_time
  118. print(
  119. "\n --- kernel matrix of random walk kernel of size %d built in %s seconds ---"
  120. % (len(Gn), run_time))
  121. return Kmatrix, run_time, idx
  122. ###############################################################################
  123. def _sylvester_equation(Gn, lmda, p, q, eweight, n_jobs):
  124. """Calculate walk graph kernels up to n between 2 graphs using Sylvester method.
  125. Parameters
  126. ----------
  127. G1, G2 : NetworkX graph
  128. Graphs between which the kernel is calculated.
  129. node_label : string
  130. node attribute used as label.
  131. edge_label : string
  132. edge attribute used as label.
  133. Return
  134. ------
  135. kernel : float
  136. Kernel between 2 graphs.
  137. """
  138. Kmatrix = np.zeros((len(Gn), len(Gn)))
  139. if q == None:
  140. # don't normalize adjacency matrices if q is a uniform vector. Note
  141. # A_wave_list accually contains the transposes of the adjacency matrices.
  142. A_wave_list = [
  143. nx.adjacency_matrix(G, eweight).todense().transpose() for G in tqdm(
  144. Gn, desc='compute adjacency matrices', file=sys.stdout)
  145. ]
  146. # # normalized adjacency matrices
  147. # A_wave_list = []
  148. # for G in tqdm(Gn, desc='compute adjacency matrices', file=sys.stdout):
  149. # A_tilde = nx.adjacency_matrix(G, eweight).todense().transpose()
  150. # norm = A_tilde.sum(axis=0)
  151. # norm[norm == 0] = 1
  152. # A_wave_list.append(A_tilde / norm)
  153. if p == None: # p is uniform distribution as default.
  154. def init_worker(Awl_toshare):
  155. global G_Awl
  156. G_Awl = Awl_toshare
  157. do_partial = partial(wrapper_se_do, lmda)
  158. parallel_gm(do_partial, Kmatrix, Gn, init_worker=init_worker,
  159. glbv=(A_wave_list,), n_jobs=n_jobs)
  160. # pbar = tqdm(
  161. # total=(1 + len(Gn)) * len(Gn) / 2,
  162. # desc='calculating kernels',
  163. # file=sys.stdout)
  164. # for i in range(0, len(Gn)):
  165. # for j in range(i, len(Gn)):
  166. # S = lmda * A_wave_list[j]
  167. # T_t = A_wave_list[i]
  168. # # use uniform distribution if there is no prior knowledge.
  169. # nb_pd = len(A_wave_list[i]) * len(A_wave_list[j])
  170. # p_times_uni = 1 / nb_pd
  171. # M0 = np.full((len(A_wave_list[j]), len(A_wave_list[i])), p_times_uni)
  172. # X = dlyap(S, T_t, M0)
  173. # X = np.reshape(X, (-1, 1), order='F')
  174. # # use uniform distribution if there is no prior knowledge.
  175. # q_times = np.full((1, nb_pd), p_times_uni)
  176. # Kmatrix[i][j] = np.dot(q_times, X)
  177. # Kmatrix[j][i] = Kmatrix[i][j]
  178. # pbar.update(1)
  179. return Kmatrix
  180. def wrapper_se_do(lmda, itr):
  181. i = itr[0]
  182. j = itr[1]
  183. return i, j, _se_do(G_Awl[i], G_Awl[j], lmda)
  184. def _se_do(A_wave1, A_wave2, lmda):
  185. from control import dlyap
  186. S = lmda * A_wave2
  187. T_t = A_wave1
  188. # use uniform distribution if there is no prior knowledge.
  189. nb_pd = len(A_wave1) * len(A_wave2)
  190. p_times_uni = 1 / nb_pd
  191. M0 = np.full((len(A_wave2), len(A_wave1)), p_times_uni)
  192. X = dlyap(S, T_t, M0)
  193. X = np.reshape(X, (-1, 1), order='F')
  194. # use uniform distribution if there is no prior knowledge.
  195. q_times = np.full((1, nb_pd), p_times_uni)
  196. return np.dot(q_times, X)
  197. ###############################################################################
  198. def _conjugate_gradient(Gn, lmda, p, q, ds_attrs, node_kernels, edge_kernels,
  199. node_label, edge_label, eweight, n_jobs):
  200. """Calculate walk graph kernels up to n between 2 graphs using conjugate method.
  201. Parameters
  202. ----------
  203. G1, G2 : NetworkX graph
  204. Graphs between which the kernel is calculated.
  205. node_label : string
  206. node attribute used as label.
  207. edge_label : string
  208. edge attribute used as label.
  209. Return
  210. ------
  211. kernel : float
  212. Kernel between 2 graphs.
  213. """
  214. Kmatrix = np.zeros((len(Gn), len(Gn)))
  215. # if not ds_attrs['node_labeled'] and ds_attrs['node_attr_dim'] < 1 and \
  216. # not ds_attrs['edge_labeled'] and ds_attrs['edge_attr_dim'] < 1:
  217. # # this is faster from unlabeled graphs. @todo: why?
  218. # if q == None:
  219. # # don't normalize adjacency matrices if q is a uniform vector. Note
  220. # # A_wave_list accually contains the transposes of the adjacency matrices.
  221. # A_wave_list = [
  222. # nx.adjacency_matrix(G, eweight).todense().transpose() for G in
  223. # tqdm(Gn, desc='compute adjacency matrices', file=sys.stdout)
  224. # ]
  225. # if p == None: # p is uniform distribution as default.
  226. # def init_worker(Awl_toshare):
  227. # global G_Awl
  228. # G_Awl = Awl_toshare
  229. # do_partial = partial(wrapper_cg_unlabled_do, lmda)
  230. # parallel_gm(do_partial, Kmatrix, Gn, init_worker=init_worker,
  231. # glbv=(A_wave_list,), n_jobs=n_jobs)
  232. # else:
  233. # reindex nodes using consecutive integers for convenience of kernel calculation.
  234. Gn = [nx.convert_node_labels_to_integers(
  235. g, first_label=0, label_attribute='label_orignal') for g in tqdm(
  236. Gn, desc='reindex vertices', file=sys.stdout)]
  237. if p == None and q == None: # p and q are uniform distributions as default.
  238. def init_worker(gn_toshare):
  239. global G_gn
  240. G_gn = gn_toshare
  241. do_partial = partial(wrapper_cg_labled_do, ds_attrs, node_kernels,
  242. node_label, edge_kernels, edge_label, lmda)
  243. parallel_gm(do_partial, Kmatrix, Gn, init_worker=init_worker,
  244. glbv=(Gn,), n_jobs=n_jobs)
  245. # pbar = tqdm(
  246. # total=(1 + len(Gn)) * len(Gn) / 2,
  247. # desc='calculating kernels',
  248. # file=sys.stdout)
  249. # for i in range(0, len(Gn)):
  250. # for j in range(i, len(Gn)):
  251. # result = _cg_labled_do(Gn[i], Gn[j], ds_attrs, node_kernels,
  252. # node_label, edge_kernels, edge_label, lmda)
  253. # Kmatrix[i][j] = result
  254. # Kmatrix[j][i] = Kmatrix[i][j]
  255. # pbar.update(1)
  256. return Kmatrix
  257. def wrapper_cg_unlabled_do(lmda, itr):
  258. i = itr[0]
  259. j = itr[1]
  260. return i, j, _cg_unlabled_do(G_Awl[i], G_Awl[j], lmda)
  261. def _cg_unlabled_do(A_wave1, A_wave2, lmda):
  262. nb_pd = len(A_wave1) * len(A_wave2)
  263. p_times_uni = 1 / nb_pd
  264. w_times = kron(A_wave1, A_wave2).todense()
  265. A = identity(w_times.shape[0]) - w_times * lmda
  266. b = np.full((nb_pd, 1), p_times_uni)
  267. x, _ = cg(A, b)
  268. # use uniform distribution if there is no prior knowledge.
  269. q_times = np.full((1, nb_pd), p_times_uni)
  270. return np.dot(q_times, x)
  271. def wrapper_cg_labled_do(ds_attrs, node_kernels, node_label, edge_kernels,
  272. edge_label, lmda, itr):
  273. i = itr[0]
  274. j = itr[1]
  275. return i, j, _cg_labled_do(G_gn[i], G_gn[j], ds_attrs, node_kernels,
  276. node_label, edge_kernels, edge_label, lmda)
  277. def _cg_labled_do(g1, g2, ds_attrs, node_kernels, node_label,
  278. edge_kernels, edge_label, lmda):
  279. # Frist, ompute kernels between all pairs of nodes, method borrowed
  280. # from FCSP. It is faster than directly computing all edge kernels
  281. # when $d_1d_2>2$, where $d_1$ and $d_2$ are vertex degrees of the
  282. # graphs compared, which is the most case we went though. For very
  283. # sparse graphs, this would be slow.
  284. vk_dict = computeVK(g1, g2, ds_attrs, node_kernels, node_label)
  285. # Compute weight matrix of the direct product graph.
  286. w_times, w_dim = computeW(g1, g2, vk_dict, ds_attrs,
  287. edge_kernels, edge_label)
  288. # use uniform distribution if there is no prior knowledge.
  289. p_times_uni = 1 / w_dim
  290. A = identity(w_times.shape[0]) - w_times * lmda
  291. b = np.full((w_dim, 1), p_times_uni)
  292. x, _ = cg(A, b)
  293. # use uniform distribution if there is no prior knowledge.
  294. q_times = np.full((1, w_dim), p_times_uni)
  295. return np.dot(q_times, x)
  296. ###############################################################################
  297. def _fixed_point(Gn, lmda, p, q, ds_attrs, node_kernels, edge_kernels,
  298. node_label, edge_label, eweight, n_jobs):
  299. """Calculate walk graph kernels up to n between 2 graphs using Fixed-Point method.
  300. Parameters
  301. ----------
  302. G1, G2 : NetworkX graph
  303. Graphs between which the kernel is calculated.
  304. node_label : string
  305. node attribute used as label.
  306. edge_label : string
  307. edge attribute used as label.
  308. Return
  309. ------
  310. kernel : float
  311. Kernel between 2 graphs.
  312. """
  313. Kmatrix = np.zeros((len(Gn), len(Gn)))
  314. # if not ds_attrs['node_labeled'] and ds_attrs['node_attr_dim'] < 1 and \
  315. # not ds_attrs['edge_labeled'] and ds_attrs['edge_attr_dim'] > 1:
  316. # # this is faster from unlabeled graphs. @todo: why?
  317. # if q == None:
  318. # # don't normalize adjacency matrices if q is a uniform vector. Note
  319. # # A_wave_list accually contains the transposes of the adjacency matrices.
  320. # A_wave_list = [
  321. # nx.adjacency_matrix(G, eweight).todense().transpose() for G in
  322. # tqdm(Gn, desc='compute adjacency matrices', file=sys.stdout)
  323. # ]
  324. # if p == None: # p is uniform distribution as default.
  325. # pbar = tqdm(
  326. # total=(1 + len(Gn)) * len(Gn) / 2,
  327. # desc='calculating kernels',
  328. # file=sys.stdout)
  329. # for i in range(0, len(Gn)):
  330. # for j in range(i, len(Gn)):
  331. # # use uniform distribution if there is no prior knowledge.
  332. # nb_pd = len(A_wave_list[i]) * len(A_wave_list[j])
  333. # p_times_uni = 1 / nb_pd
  334. # w_times = kron(A_wave_list[i], A_wave_list[j]).todense()
  335. # p_times = np.full((nb_pd, 1), p_times_uni)
  336. # x = fixed_point(func_fp, p_times, args=(p_times, lmda, w_times))
  337. # # use uniform distribution if there is no prior knowledge.
  338. # q_times = np.full((1, nb_pd), p_times_uni)
  339. # Kmatrix[i][j] = np.dot(q_times, x)
  340. # Kmatrix[j][i] = Kmatrix[i][j]
  341. # pbar.update(1)
  342. # else:
  343. # reindex nodes using consecutive integers for convenience of kernel calculation.
  344. Gn = [nx.convert_node_labels_to_integers(
  345. g, first_label=0, label_attribute='label_orignal') for g in tqdm(
  346. Gn, desc='reindex vertices', file=sys.stdout)]
  347. if p == None and q == None: # p and q are uniform distributions as default.
  348. def init_worker(gn_toshare):
  349. global G_gn
  350. G_gn = gn_toshare
  351. do_partial = partial(wrapper_fp_labled_do, ds_attrs, node_kernels,
  352. node_label, edge_kernels, edge_label, lmda)
  353. parallel_gm(do_partial, Kmatrix, Gn, init_worker=init_worker,
  354. glbv=(Gn,), n_jobs=n_jobs)
  355. return Kmatrix
  356. def wrapper_fp_labled_do(ds_attrs, node_kernels, node_label, edge_kernels,
  357. edge_label, lmda, itr):
  358. i = itr[0]
  359. j = itr[1]
  360. return i, j, _fp_labled_do(G_gn[i], G_gn[j], ds_attrs, node_kernels,
  361. node_label, edge_kernels, edge_label, lmda)
  362. def _fp_labled_do(g1, g2, ds_attrs, node_kernels, node_label,
  363. edge_kernels, edge_label, lmda):
  364. # Frist, ompute kernels between all pairs of nodes, method borrowed
  365. # from FCSP. It is faster than directly computing all edge kernels
  366. # when $d_1d_2>2$, where $d_1$ and $d_2$ are vertex degrees of the
  367. # graphs compared, which is the most case we went though. For very
  368. # sparse graphs, this would be slow.
  369. vk_dict = computeVK(g1, g2, ds_attrs, node_kernels, node_label)
  370. # Compute weight matrix of the direct product graph.
  371. w_times, w_dim = computeW(g1, g2, vk_dict, ds_attrs,
  372. edge_kernels, edge_label)
  373. # use uniform distribution if there is no prior knowledge.
  374. p_times_uni = 1 / w_dim
  375. p_times = np.full((w_dim, 1), p_times_uni)
  376. x = fixed_point(func_fp, p_times, args=(p_times, lmda, w_times),
  377. xtol=1e-06, maxiter=1000)
  378. # use uniform distribution if there is no prior knowledge.
  379. q_times = np.full((1, w_dim), p_times_uni)
  380. return np.dot(q_times, x)
  381. def func_fp(x, p_times, lmda, w_times):
  382. haha = w_times * x
  383. haha = lmda * haha
  384. haha = p_times + haha
  385. return p_times + lmda * np.dot(w_times, x)
  386. ###############################################################################
  387. def _spectral_decomposition(Gn, weight, p, q, sub_kernel, eweight, n_jobs):
  388. """Calculate walk graph kernels up to n between 2 unlabeled graphs using
  389. spectral decomposition method. Labels will be ignored.
  390. Parameters
  391. ----------
  392. G1, G2 : NetworkX graph
  393. Graphs between which the kernel is calculated.
  394. node_label : string
  395. node attribute used as label.
  396. edge_label : string
  397. edge attribute used as label.
  398. Return
  399. ------
  400. kernel : float
  401. Kernel between 2 graphs.
  402. """
  403. Kmatrix = np.zeros((len(Gn), len(Gn)))
  404. if q == None:
  405. # precompute the spectral decomposition of each graph.
  406. P_list = []
  407. D_list = []
  408. for G in tqdm(Gn, desc='spectral decompose', file=sys.stdout):
  409. # don't normalize adjacency matrices if q is a uniform vector. Note
  410. # A accually is the transpose of the adjacency matrix.
  411. A = nx.adjacency_matrix(G, eweight).todense().transpose()
  412. ew, ev = np.linalg.eig(A)
  413. D_list.append(ew)
  414. P_list.append(ev)
  415. # P_inv_list = [p.T for p in P_list] # @todo: also works for directed graphs?
  416. if p == None: # p is uniform distribution as default.
  417. q_T_list = [np.full((1, nx.number_of_nodes(G)), 1 / nx.number_of_nodes(G)) for G in Gn]
  418. # q_T_list = [q.T for q in q_list]
  419. def init_worker(q_T_toshare, P_toshare, D_toshare):
  420. global G_q_T, G_P, G_D
  421. G_q_T = q_T_toshare
  422. G_P = P_toshare
  423. G_D = D_toshare
  424. do_partial = partial(wrapper_sd_do, weight, sub_kernel)
  425. parallel_gm(do_partial, Kmatrix, Gn, init_worker=init_worker,
  426. glbv=(q_T_list, P_list, D_list), n_jobs=n_jobs)
  427. # pbar = tqdm(
  428. # total=(1 + len(Gn)) * len(Gn) / 2,
  429. # desc='calculating kernels',
  430. # file=sys.stdout)
  431. # for i in range(0, len(Gn)):
  432. # for j in range(i, len(Gn)):
  433. # result = _sd_do(q_T_list[i], q_T_list[j], P_list[i], P_list[j],
  434. # D_list[i], D_list[j], weight, sub_kernel)
  435. # Kmatrix[i][j] = result
  436. # Kmatrix[j][i] = Kmatrix[i][j]
  437. # pbar.update(1)
  438. return Kmatrix
  439. def wrapper_sd_do(weight, sub_kernel, itr):
  440. i = itr[0]
  441. j = itr[1]
  442. return i, j, _sd_do(G_q_T[i], G_q_T[j], G_P[i], G_P[j], G_D[i], G_D[j],
  443. weight, sub_kernel)
  444. def _sd_do(q_T1, q_T2, P1, P2, D1, D2, weight, sub_kernel):
  445. # use uniform distribution if there is no prior knowledge.
  446. kl = kron(np.dot(q_T1, P1), np.dot(q_T2, P2)).todense()
  447. # @todo: this is not be needed when p = q (kr = kl.T) for undirected graphs
  448. # kr = kron(np.dot(P_inv_list[i], q_list[i]), np.dot(P_inv_list[j], q_list[j])).todense()
  449. if sub_kernel == 'exp':
  450. D_diag = np.array([d1 * d2 for d1 in D1 for d2 in D2])
  451. kmiddle = np.diag(np.exp(weight * D_diag))
  452. elif sub_kernel == 'geo':
  453. D_diag = np.array([d1 * d2 for d1 in D1 for d2 in D2])
  454. kmiddle = np.diag(weight * D_diag)
  455. kmiddle = np.identity(len(kmiddle)) - weight * kmiddle
  456. kmiddle = np.linalg.inv(kmiddle)
  457. return np.dot(np.dot(kl, kmiddle), kl.T)[0, 0]
  458. ###############################################################################
  459. def _randomwalkkernel_kron(G1, G2, node_label, edge_label):
  460. """Calculate walk graph kernels up to n between 2 graphs using nearest Kronecker product approximation method.
  461. Parameters
  462. ----------
  463. G1, G2 : NetworkX graph
  464. Graphs between which the kernel is calculated.
  465. node_label : string
  466. node attribute used as label.
  467. edge_label : string
  468. edge attribute used as label.
  469. Return
  470. ------
  471. kernel : float
  472. Kernel between 2 graphs.
  473. """
  474. pass
  475. ###############################################################################
  476. def getLabels(Gn, node_label, edge_label, directed):
  477. """Get symbolic labels of a graph dataset, where vertex labels are dealt
  478. with by concatenating them to the edge labels of adjacent edges.
  479. """
  480. label_list = []
  481. label_set = set()
  482. for g in Gn:
  483. label_g = {}
  484. for e in g.edges(data=True):
  485. nl1 = g.node[e[0]][node_label]
  486. nl2 = g.node[e[1]][node_label]
  487. if not directed and nl1 > nl2:
  488. nl1, nl2 = nl2, nl1
  489. label = (nl1, e[2][edge_label], nl2)
  490. label_g[(e[0], e[1])] = label
  491. label_list.append(label_g)
  492. label_set = set([l for lg in label_list for l in lg.values()])
  493. return label_list, len(label_set)
  494. def filterGramMatrix(gmt, label_dict, label, directed):
  495. """Compute (the transpose of) the Gram matrix filtered by a label.
  496. """
  497. gmf = np.zeros(gmt.shape)
  498. for (n1, n2), l in label_dict.items():
  499. if l == label:
  500. gmf[n2, n1] = gmt[n2, n1]
  501. if not directed:
  502. gmf[n1, n2] = gmt[n1, n2]
  503. return gmf
  504. def computeVK(g1, g2, ds_attrs, node_kernels, node_label):
  505. '''Compute vertex kernels between vertices of two graphs.
  506. '''
  507. vk_dict = {} # shortest path matrices dict
  508. if ds_attrs['node_labeled']:
  509. # node symb and non-synb labeled
  510. if ds_attrs['node_attr_dim'] > 0:
  511. kn = node_kernels['mix']
  512. for n1 in g1.nodes(data=True):
  513. for n2 in g2.nodes(data=True):
  514. vk_dict[(n1[0], n2[0])] = kn(
  515. n1[1][node_label], n2[1][node_label],
  516. n1[1]['attributes'], n2[1]['attributes'])
  517. # node symb labeled
  518. else:
  519. kn = node_kernels['symb']
  520. for n1 in g1.nodes(data=True):
  521. for n2 in g2.nodes(data=True):
  522. vk_dict[(n1[0], n2[0])] = kn(n1[1][node_label],
  523. n2[1][node_label])
  524. else:
  525. # node non-synb labeled
  526. if ds_attrs['node_attr_dim'] > 0:
  527. kn = node_kernels['nsymb']
  528. for n1 in g1.nodes(data=True):
  529. for n2 in g2.nodes(data=True):
  530. vk_dict[(n1[0], n2[0])] = kn(n1[1]['attributes'],
  531. n2[1]['attributes'])
  532. # node unlabeled
  533. else:
  534. pass
  535. return vk_dict
  536. def computeW(g1, g2, vk_dict, ds_attrs, edge_kernels, edge_label):
  537. '''Compute weight matrix of the direct product graph.
  538. '''
  539. w_dim = nx.number_of_nodes(g1) * nx.number_of_nodes(g2)
  540. w_times = np.zeros((w_dim, w_dim))
  541. if vk_dict: # node labeled
  542. if ds_attrs['is_directed']:
  543. if ds_attrs['edge_labeled']:
  544. # edge symb and non-synb labeled
  545. if ds_attrs['edge_attr_dim'] > 0:
  546. ke = edge_kernels['mix']
  547. for e1 in g1.edges(data=True):
  548. for e2 in g2.edges(data=True):
  549. ek_temp = ke(e1[2][edge_label], e2[2][edge_label],
  550. e1[2]['attributes'], e2[2]['attributes'])
  551. w_idx = (e1[0] * nx.number_of_nodes(g2) + e2[0],
  552. e1[1] * nx.number_of_nodes(g2) + e2[1])
  553. w_times[w_idx] = vk_dict[(e1[0], e2[0])] \
  554. * ek_temp * vk_dict[(e1[1], e2[1])]
  555. # edge symb labeled
  556. else:
  557. ke = edge_kernels['symb']
  558. for e1 in g1.edges(data=True):
  559. for e2 in g2.edges(data=True):
  560. ek_temp = ke(e1[2][edge_label], e2[2][edge_label])
  561. w_idx = (e1[0] * nx.number_of_nodes(g2) + e2[0],
  562. e1[1] * nx.number_of_nodes(g2) + e2[1])
  563. w_times[w_idx] = vk_dict[(e1[0], e2[0])] \
  564. * ek_temp * vk_dict[(e1[1], e2[1])]
  565. else:
  566. # edge non-synb labeled
  567. if ds_attrs['edge_attr_dim'] > 0:
  568. ke = edge_kernels['nsymb']
  569. for e1 in g1.edges(data=True):
  570. for e2 in g2.edges(data=True):
  571. ek_temp = ke(e1[2]['attributes'], e2[2]['attributes'])
  572. w_idx = (e1[0] * nx.number_of_nodes(g2) + e2[0],
  573. e1[1] * nx.number_of_nodes(g2) + e2[1])
  574. w_times[w_idx] = vk_dict[(e1[0], e2[0])] \
  575. * ek_temp * vk_dict[(e1[1], e2[1])]
  576. # edge unlabeled
  577. else:
  578. for e1 in g1.edges(data=True):
  579. for e2 in g2.edges(data=True):
  580. w_idx = (e1[0] * nx.number_of_nodes(g2) + e2[0],
  581. e1[1] * nx.number_of_nodes(g2) + e2[1])
  582. w_times[w_idx] = vk_dict[(e1[0], e2[0])] \
  583. * vk_dict[(e1[1], e2[1])]
  584. else: # undirected
  585. if ds_attrs['edge_labeled']:
  586. # edge symb and non-synb labeled
  587. if ds_attrs['edge_attr_dim'] > 0:
  588. ke = edge_kernels['mix']
  589. for e1 in g1.edges(data=True):
  590. for e2 in g2.edges(data=True):
  591. ek_temp = ke(e1[2][edge_label], e2[2][edge_label],
  592. e1[2]['attributes'], e2[2]['attributes'])
  593. w_idx = (e1[0] * nx.number_of_nodes(g2) + e2[0],
  594. e1[1] * nx.number_of_nodes(g2) + e2[1])
  595. w_times[w_idx] = vk_dict[(e1[0], e2[0])] \
  596. * ek_temp * vk_dict[(e1[1], e2[1])] \
  597. + vk_dict[(e1[0], e2[1])] \
  598. * ek_temp * vk_dict[(e1[1], e2[0])]
  599. w_times[w_idx[1], w_idx[0]] = w_times[w_idx[0], w_idx[1]]
  600. w_idx2 = (e1[0] * nx.number_of_nodes(g2) + e2[1],
  601. e1[1] * nx.number_of_nodes(g2) + e2[0])
  602. w_times[w_idx2[0], w_idx2[1]] = w_times[w_idx[0], w_idx[1]]
  603. w_times[w_idx2[1], w_idx2[0]] = w_times[w_idx[0], w_idx[1]]
  604. # edge symb labeled
  605. else:
  606. ke = edge_kernels['symb']
  607. for e1 in g1.edges(data=True):
  608. for e2 in g2.edges(data=True):
  609. ek_temp = ke(e1[2][edge_label], e2[2][edge_label])
  610. w_idx = (e1[0] * nx.number_of_nodes(g2) + e2[0],
  611. e1[1] * nx.number_of_nodes(g2) + e2[1])
  612. w_times[w_idx] = vk_dict[(e1[0], e2[0])] \
  613. * ek_temp * vk_dict[(e1[1], e2[1])] \
  614. + vk_dict[(e1[0], e2[1])] \
  615. * ek_temp * vk_dict[(e1[1], e2[0])]
  616. w_times[w_idx[1], w_idx[0]] = w_times[w_idx[0], w_idx[1]]
  617. w_idx2 = (e1[0] * nx.number_of_nodes(g2) + e2[1],
  618. e1[1] * nx.number_of_nodes(g2) + e2[0])
  619. w_times[w_idx2[0], w_idx2[1]] = w_times[w_idx[0], w_idx[1]]
  620. w_times[w_idx2[1], w_idx2[0]] = w_times[w_idx[0], w_idx[1]]
  621. else:
  622. # edge non-synb labeled
  623. if ds_attrs['edge_attr_dim'] > 0:
  624. ke = edge_kernels['nsymb']
  625. for e1 in g1.edges(data=True):
  626. for e2 in g2.edges(data=True):
  627. ek_temp = ke(e1[2]['attributes'], e2[2]['attributes'])
  628. w_idx = (e1[0] * nx.number_of_nodes(g2) + e2[0],
  629. e1[1] * nx.number_of_nodes(g2) + e2[1])
  630. w_times[w_idx] = vk_dict[(e1[0], e2[0])] \
  631. * ek_temp * vk_dict[(e1[1], e2[1])] \
  632. + vk_dict[(e1[0], e2[1])] \
  633. * ek_temp * vk_dict[(e1[1], e2[0])]
  634. w_times[w_idx[1], w_idx[0]] = w_times[w_idx[0], w_idx[1]]
  635. w_idx2 = (e1[0] * nx.number_of_nodes(g2) + e2[1],
  636. e1[1] * nx.number_of_nodes(g2) + e2[0])
  637. w_times[w_idx2[0], w_idx2[1]] = w_times[w_idx[0], w_idx[1]]
  638. w_times[w_idx2[1], w_idx2[0]] = w_times[w_idx[0], w_idx[1]]
  639. # edge unlabeled
  640. else:
  641. for e1 in g1.edges(data=True):
  642. for e2 in g2.edges(data=True):
  643. w_idx = (e1[0] * nx.number_of_nodes(g2) + e2[0],
  644. e1[1] * nx.number_of_nodes(g2) + e2[1])
  645. w_times[w_idx] = vk_dict[(e1[0], e2[0])] \
  646. * vk_dict[(e1[1], e2[1])] \
  647. + vk_dict[(e1[0], e2[1])] \
  648. * vk_dict[(e1[1], e2[0])]
  649. w_times[w_idx[1], w_idx[0]] = w_times[w_idx[0], w_idx[1]]
  650. w_idx2 = (e1[0] * nx.number_of_nodes(g2) + e2[1],
  651. e1[1] * nx.number_of_nodes(g2) + e2[0])
  652. w_times[w_idx2[0], w_idx2[1]] = w_times[w_idx[0], w_idx[1]]
  653. w_times[w_idx2[1], w_idx2[0]] = w_times[w_idx[0], w_idx[1]]
  654. else: # node unlabeled
  655. if ds_attrs['is_directed']:
  656. if ds_attrs['edge_labeled']:
  657. # edge symb and non-synb labeled
  658. if ds_attrs['edge_attr_dim'] > 0:
  659. ke = edge_kernels['mix']
  660. for e1 in g1.edges(data=True):
  661. for e2 in g2.edges(data=True):
  662. ek_temp = ke(e1[2][edge_label], e2[2][edge_label],
  663. e1[2]['attributes'], e2[2]['attributes'])
  664. w_idx = (e1[0] * nx.number_of_nodes(g2) + e2[0],
  665. e1[1] * nx.number_of_nodes(g2) + e2[1])
  666. w_times[w_idx] = ek_temp
  667. # edge symb labeled
  668. else:
  669. ke = edge_kernels['symb']
  670. for e1 in g1.edges(data=True):
  671. for e2 in g2.edges(data=True):
  672. ek_temp = ke(e1[2][edge_label], e2[2][edge_label])
  673. w_idx = (e1[0] * nx.number_of_nodes(g2) + e2[0],
  674. e1[1] * nx.number_of_nodes(g2) + e2[1])
  675. w_times[w_idx] = ek_temp
  676. else:
  677. # edge non-synb labeled
  678. if ds_attrs['edge_attr_dim'] > 0:
  679. ke = edge_kernels['nsymb']
  680. for e1 in g1.edges(data=True):
  681. for e2 in g2.edges(data=True):
  682. ek_temp = ke(e1[2]['attributes'], e2[2]['attributes'])
  683. w_idx = (e1[0] * nx.number_of_nodes(g2) + e2[0],
  684. e1[1] * nx.number_of_nodes(g2) + e2[1])
  685. w_times[w_idx] = ek_temp
  686. # edge unlabeled
  687. else:
  688. for e1 in g1.edges(data=True):
  689. for e2 in g2.edges(data=True):
  690. w_idx = (e1[0] * nx.number_of_nodes(g2) + e2[0],
  691. e1[1] * nx.number_of_nodes(g2) + e2[1])
  692. w_times[w_idx] = 1
  693. else: # undirected
  694. if ds_attrs['edge_labeled']:
  695. # edge symb and non-synb labeled
  696. if ds_attrs['edge_attr_dim'] > 0:
  697. ke = edge_kernels['mix']
  698. for e1 in g1.edges(data=True):
  699. for e2 in g2.edges(data=True):
  700. ek_temp = ke(e1[2][edge_label], e2[2][edge_label],
  701. e1[2]['attributes'], e2[2]['attributes'])
  702. w_idx = (e1[0] * nx.number_of_nodes(g2) + e2[0],
  703. e1[1] * nx.number_of_nodes(g2) + e2[1])
  704. w_times[w_idx] = ek_temp
  705. w_times[w_idx[1], w_idx[0]] = w_times[w_idx[0], w_idx[1]]
  706. w_idx2 = (e1[0] * nx.number_of_nodes(g2) + e2[1],
  707. e1[1] * nx.number_of_nodes(g2) + e2[0])
  708. w_times[w_idx2[0], w_idx2[1]] = w_times[w_idx[0], w_idx[1]]
  709. w_times[w_idx2[1], w_idx2[0]] = w_times[w_idx[0], w_idx[1]]
  710. # edge symb labeled
  711. else:
  712. ke = edge_kernels['symb']
  713. for e1 in g1.edges(data=True):
  714. for e2 in g2.edges(data=True):
  715. ek_temp = ke(e1[2][edge_label], e2[2][edge_label])
  716. w_idx = (e1[0] * nx.number_of_nodes(g2) + e2[0],
  717. e1[1] * nx.number_of_nodes(g2) + e2[1])
  718. w_times[w_idx] = ek_temp
  719. w_times[w_idx[1], w_idx[0]] = w_times[w_idx[0], w_idx[1]]
  720. w_idx2 = (e1[0] * nx.number_of_nodes(g2) + e2[1],
  721. e1[1] * nx.number_of_nodes(g2) + e2[0])
  722. w_times[w_idx2[0], w_idx2[1]] = w_times[w_idx[0], w_idx[1]]
  723. w_times[w_idx2[1], w_idx2[0]] = w_times[w_idx[0], w_idx[1]]
  724. else:
  725. # edge non-synb labeled
  726. if ds_attrs['edge_attr_dim'] > 0:
  727. ke = edge_kernels['nsymb']
  728. for e1 in g1.edges(data=True):
  729. for e2 in g2.edges(data=True):
  730. ek_temp = ke(e1[2]['attributes'], e2[2]['attributes'])
  731. w_idx = (e1[0] * nx.number_of_nodes(g2) + e2[0],
  732. e1[1] * nx.number_of_nodes(g2) + e2[1])
  733. w_times[w_idx] = ek_temp
  734. w_times[w_idx[1], w_idx[0]] = w_times[w_idx[0], w_idx[1]]
  735. w_idx2 = (e1[0] * nx.number_of_nodes(g2) + e2[1],
  736. e1[1] * nx.number_of_nodes(g2) + e2[0])
  737. w_times[w_idx2[0], w_idx2[1]] = w_times[w_idx[0], w_idx[1]]
  738. w_times[w_idx2[1], w_idx2[0]] = w_times[w_idx[0], w_idx[1]]
  739. # edge unlabeled
  740. else:
  741. for e1 in g1.edges(data=True):
  742. for e2 in g2.edges(data=True):
  743. w_idx = (e1[0] * nx.number_of_nodes(g2) + e2[0],
  744. e1[1] * nx.number_of_nodes(g2) + e2[1])
  745. w_times[w_idx] = 1
  746. w_times[w_idx[1], w_idx[0]] = w_times[w_idx[0], w_idx[1]]
  747. w_idx2 = (e1[0] * nx.number_of_nodes(g2) + e2[1],
  748. e1[1] * nx.number_of_nodes(g2) + e2[0])
  749. w_times[w_idx2[0], w_idx2[1]] = w_times[w_idx[0], w_idx[1]]
  750. w_times[w_idx2[1], w_idx2[0]] = w_times[w_idx[0], w_idx[1]]
  751. return w_times, w_dim

A Python package for graph kernels, graph edit distances and graph pre-image problem.