All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.intel.analytics.zoo.models.recommendation.NeuralCF.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2018 Analytics Zoo Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.intel.analytics.zoo.models.recommendation

import com.intel.analytics.bigdl.nn.Graph.ModuleNode
import com.intel.analytics.bigdl.nn.abstractnn.AbstractModule
import com.intel.analytics.bigdl.tensor.Tensor
import com.intel.analytics.bigdl.tensor.TensorNumericMath.TensorNumeric
import com.intel.analytics.bigdl.utils.Shape
import com.intel.analytics.zoo.models.common.ZooModel
import com.intel.analytics.zoo.pipeline.api.keras.layers._
import com.intel.analytics.zoo.pipeline.api.keras.models.Model

import scala.reflect.ClassTag

/**
 * The neural collaborative filtering model used for recommendation.
 * @param userCount    The number of users. Positive integer.
 * @param itemCount    The number of items. Positive integer.
 * @param numClasses   The number of classes. Positive integer.
 * @param userEmbed    Units of user embedding. Positive integer. Default is 20.
 * @param itemEmbed    Units of item embedding. Positive integer. Default is 20.
 * @param hiddenLayers Units hiddenLayers for MLP. Array of positive integers.
 *                     Default is Array(40, 20, 10).
 * @param includeMF    Whether to include Matrix Factorization. Boolean. Default is true.
 * @param mfEmbed      Units of matrix factorization embedding. Positive integer.
 *                     Default is 20.
 * @tparam T Numeric type of parameter(e.g. weight, bias). Only support float/double now.
 */

class NeuralCF[T: ClassTag](
    val userCount: Int,
    val itemCount: Int,
    val numClasses: Int,
    val userEmbed: Int = 20,
    val itemEmbed: Int = 20,
    val hiddenLayers: Array[Int] = Array(40, 20, 10),
    val includeMF: Boolean = true,
    val mfEmbed: Int = 20)(implicit ev: TensorNumeric[T])
  extends Recommender[T] {

  override def buildModel(): AbstractModule[Tensor[T], Tensor[T], T] = {
    val input = Input[T](inputShape = Shape(2))

    val userSelect = Select[T](1, 0).inputs(input)
    val itemSelect = Select[T](1, 1).inputs(input)

    val userFlat = Flatten().inputs(userSelect)
    val itemFlat = Flatten().inputs(itemSelect)

    val mlpUserTable = Embedding[T](userCount + 1, userEmbed, init = "normal")
    val mlpItemTable = Embedding[T](itemCount + 1, itemEmbed, init = "normal")

    val mlpUserLatent: ModuleNode[T] = Flatten().inputs(mlpUserTable.inputs(userFlat))
    val mlpItemLatent: ModuleNode[T] = Flatten().inputs(mlpItemTable.inputs(itemFlat))

    val mlpEmbeddedLayer = Merge.merge[T](List(mlpUserLatent, mlpItemLatent), "concat", 1)

    val linear1 = Dense[T](hiddenLayers(0), activation = "relu").inputs(mlpEmbeddedLayer)
    var mlpLinear = linear1
    for (i <- 1 to hiddenLayers.length - 1) {
      val linearMid = Dense(hiddenLayers(i), activation = "relu").inputs(mlpLinear)
      mlpLinear = linearMid
    }

    val linearLast = if (includeMF) {
      require(mfEmbed > 0, s"please provide meaningful number of embedding units")

      val mfUserTable = Embedding[T](userCount + 1, mfEmbed, init = "normal")
      val mfItemTable = Embedding[T](itemCount + 1, mfEmbed, init = "normal")

      val mfUserLatent: ModuleNode[T] = Flatten().inputs(mfUserTable.inputs(userFlat))
      val mfItemLatent: ModuleNode[T] = Flatten().inputs(mfItemTable.inputs(itemFlat))

      val mfEmbeddedLayer = Merge.merge[T](List(mfUserLatent, mfItemLatent), "mul", 1)

      val concatedModel = Merge.merge[T](List(mlpLinear, mfEmbeddedLayer), "concat", 1)

      Dense(numClasses, activation = "softmax").inputs(concatedModel)
    }
    else {
      Dense(numClasses, activation = "softmax").inputs(mlpLinear)
    }

    val model = Model[T](input, linearLast)

    model.asInstanceOf[AbstractModule[Tensor[T], Tensor[T], T]]
  }
}

object NeuralCF {
  /**
   * The factory method to create a NeuralCF instance.
   */
  def apply[@specialized(Float, Double) T: ClassTag](
      userCount: Int,
      itemCount: Int,
      numClasses: Int,
      userEmbed: Int = 20,
      itemEmbed: Int = 20,
      hiddenLayers: Array[Int] = Array(40, 20, 10),
      includeMF: Boolean = true,
      mfEmbed: Int = 20)(implicit ev: TensorNumeric[T]): NeuralCF[T] = {
    new NeuralCF[T](userCount, itemCount, numClasses, userEmbed,
      itemEmbed, hiddenLayers, includeMF, mfEmbed).build()
  }

  /**
   * Load an existing NeuralCF model (with weights).
   *
   * @param path       The path for the pre-defined model.
   *                   Local file system, HDFS and Amazon S3 are supported.
   *                   HDFS path should be like "hdfs://[host]:[port]/xxx".
   *                   Amazon S3 path should be like "s3a://bucket/xxx".
   * @param weightPath The path for pre-trained weights if any. Default is null.
   * @tparam T Numeric type of parameter(e.g. weight, bias). Only support float/double now.
   */
  def loadModel[T: ClassTag](
      path: String,
      weightPath: String = null)(implicit ev: TensorNumeric[T]): NeuralCF[T] = {
    ZooModel.loadModel(path, weightPath).asInstanceOf[NeuralCF[T]]
  }
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy