Customize Models ================ One common demand for graph embedding is to customize the model (i.e. score function). Here we will demonstrate an example of adding a new model to the knowledge graph solver. First, get into ``include/model/knowledge_graph.h``. Fork an existing model class (e.g. TransE) and change it to a new name. .. code-block:: c++ template class TransE { __host__ __device__ static void forward(...); template __host__ __device__ static void backward(...); template __host__ __device__ static void backward(...); template __host__ __device__ static void backward(...); } Here a model class contains a forward function and several overloads of the backward function, which correspond to different categories of optimizers. We are going to modify a forward and a backward function, and then do some copy-and-paste work to the others. Let's start from the forward function. This function takes a triplet of embedding vectors, and outputs a score. .. code-block:: c++ void forward(const Vector &head, const Vector &tail, const Vector &relation, Float &output, float margin) The last argument is either margin for latent distance model or l3 regularization for tensor decomposition models. For TransE, the function is implemented as .. code-block:: c++ output = 0; FOR(i, dim) output += abs(head[i] + relation[i] - tail[i]); output = margin - SUM(output); Here we need to replace this piece of code with our own formulas. Note that this function should be compatible with both CPU and GPU. This can be easily achieved by helper macros defined in GraphVite. We just need to use the macro ``FOR(i, stop)`` instead of the conventional ``for (int i = 0; i < stop; i++)``. For any accumulator ``x`` inside the loop (e.g. ``output`` in this case), update it with ``x = SUM(x)`` after the loop to get the correct value. For the backward function. It takes additional arguments of moment statistics, head gradient, optimizer and sample weight. For example, here is an overload with 1 moment per embedding. .. code-block:: c++ template void backward(Vector &head, Vector &tail, Vector &relation, Vector &head_moment1, Vector &tail_moment1, Vector &relation_moment1, float margin, Float gradient, const Optimizer &optimizer, Float weight) The backward function should compute the gradient for each embedding, and update them with the optimizer. Typically, this is implemented as .. code-block:: c++ auto update = get_update_function_1_moment(); FOR(i, dim) { Float h = head[i]; Float t = tail[i]; Float r = relation[i]; Float s = h + r - t > 0 ? 1 : -1; head[i] -= (optimizer.*update)(h, -gradient * s, head_moment1[i], weight); tail[i] -= (optimizer.*update)(t, gradient * s, tail_moment1[i], weight); relation[i] -= (optimizer.*update)(r, -gradient * s, relation_moment1[i], weight); } Here we modify this function according to the partial derivatives of our forward function. Once we complete a backward function, we can copy them to the other overloads. The only difference among overloads is that they use different update function and numbers of moment statistics. Finally, we have to let the solver know there is a new model. In ``instance/knowledge_graph.cuh``, add the name of your model in ``get_available_models()``. Also add run-time dispatch of the new model in ``train_dispatch()`` and ``predict_dispatch()``. .. code-block:: c++ switch (num_moment) { case 0: if (solver->model == ...) ... case 1: if (solver->model == ...) ... case 2: if (solver->model == ...) ... Compile the source and it should be ready.