Two-class classification model with multi-type input data As a first introduction to machine learning and keras, I just finished reading Deep Learning with R by François Chollet with J.J. Allaire. I would like to extend the book's IMDB example of two-class classification to a multi-input version using functional API.

library(keras)

## Keras IMDB example

#load data
imdb &lt;- dataset_imdb(num_words = 10000)
train_data &lt;- imdb$train$x
train_labels &lt;- imdb$train$y
test_data &lt;- imdb$test$x
test_labels &lt;- imdb$test$y

#Encoding the integer sequences into a binary matrix
vectorize_sequences &lt;- function(sequences, dimension = 10000) {
results &lt;- matrix(0, nrow = length(sequences), ncol = dimension)
for (i in 1:length(sequences))
results[i, sequences[[i]]] &lt;- 1
results
}

x_train &lt;- vectorize_sequences(train_data)
x_test &lt;- vectorize_sequences(test_data)
y_train &lt;- as.numeric(train_labels)
y_test &lt;- as.numeric(test_labels)

#Defining the model
model &lt;- keras_model_sequential() %&gt;%
layer_dense(units = 16, activation = "relu", input_shape = c(10000)) %&gt;%
layer_dense(units = 16, activation = "relu") %&gt;%
layer_dense(units = 1, activation = "sigmoid")

#Compiling the model 
model %&gt;% compile(
optimizer = "rmsprop",
loss = "binary_crossentropy",
metrics = c("accuracy")
)

#Training the model
history &lt;- model %&gt;% fit(
partial_x_train,
partial_y_train,
epochs = 20,
batch_size = 512,
validation_data = list(x_val, y_val)
)

#Defining the model using functional API
model &lt;- keras_model_sequential() %&gt;%
layer_embedding(input_dim = 10000, output_dim = 8,
input_length = maxlen) %&gt;%
layer_flatten() %&gt;%
layer_dense(units = 1, activation = "sigmoid")


So, I would like to create a similar model where we predict class A and B based on 1 continuous and 3 categorical input data. In this dummy example, continuous1, categorical1 and categorical2 are 1D tensors while similarly to the IMDB example, categorical3 is a 2D tensor of shape (samples, indices) with length num_index=20 and are one-hot encoded.



#Dummy data
dat &lt;- data.frame(samples=c(rep(paste0("A_",1:9800)),rep(paste0("B_",9801:10000))))
dat$label &lt;- c(rep("A",9800),rep("B",200))
dat$continuous1 &lt;- c(rnorm(9800, 100, 45),rnorm(200, 0, 25)) #may    normalize to unit variance
dat$continuous1[dat$continuous1&lt;0] &lt;- 0
dat$categorical1 &lt;- c(rep(1:100,98),rep(1:100,2))
dat$categorical2 &lt;- c(rep(1:98,100),rep(99:100,100))

pool_A &lt;- factor(1:15,levels=1:20)
pool_B &lt;- factor(16:20,levels=1:20)
dat_categorical3 &lt;- vector("list",10000)
names(dat_categorical3) &lt;- c(as.character(dat[dat$label=="A",]$samples),as.character(dat[dat$label=="B",]$samples))
for(i in 1:9800){
  dat_categorical3[[i]] &lt;- (as.numeric(table(sample(pool_A, 20, replace=T))) &gt; 0) + 0L
}
for(i in 9801:10000){
  dat_categorical3[[i]] &lt;- (as.numeric(table(sample(pool_B, 20, replace=T))) &gt; 0) + 0L
}


As I've never built a Keras model before, I'm a bit confused on how to set up the layers representing the different data inputs using functional API. Any suggestions and feedback are highly appreciated, thanks!
With the help from a colleague, we came up with this:

set.seed(666)

#Dummy data
dat &lt;- data.frame(samples=c(rep(paste0("A_",1:9800)),rep(paste0("B_",9801:10000))))
dat$label &lt;- c(rep("A",9800),rep("B",200))
dat$continuous1 &lt;- c(rnorm(9800, 100, 45),rnorm(200, 0, 25))
dat$continuous1[dat$continuous1&lt;0] &lt;- 0
dat$categorical1 &lt;- c(rep(1:100,98),rep(1:100,2))
dat$categorical2 &lt;- c(rep(1:98,100),rep(99:100,100))

pool_A &lt;- factor(1:15,levels=1:20)
pool_B &lt;- factor(16:20,levels=1:20)
dat_categorical3 &lt;- vector("list",10000)
names(dat_categorical3) &lt;- c(as.character(dat[dat$label=="A",]$samples),as.character(dat[dat$label=="B",]$samples))
for(i in 1:9800){
  dat_categorical3[[i]] &lt;- (as.numeric(table(sample(pool_A, 20, replace=T))) &gt; 0) + 0L
}
for(i in 9801:10000){
  dat_categorical3[[i]] &lt;- (as.numeric(table(sample(pool_B, 20, replace=T))) &gt; 0) + 0L
}
dat_categorical3_tensor &lt;- do.call(rbind,dat_categorical3)


## Split data for training-validating-testing

# As we have much fewer Bs than As, each partition has to have a more or less equal number of Bs(?)
training_Bs &lt;- sample(x=as.character(dat[dat$label=="B",]$samples), size=67, replace=F)
validating_Bs &lt;- sample(x=setdiff(as.character(dat[dat$label=="B",]$samples),training_Bs), size=67, replace=F)
testing_Bs &lt;- setdiff(as.character(dat[dat$label=="B",]$samples),c(training_Bs,validating_Bs))

# We also parition the data containing As equally, though this could probably be done in e.g., 80-10-10
training_As &lt;- sample(x=as.character(dat[dat$label=="A",]$samples), size=3267, replace=F)
validating_As &lt;- sample(x=setdiff(as.character(dat[dat$label=="A",]$samples),training_As), size=3267, replace=F)
testing_As &lt;- setdiff(as.character(dat[dat$label=="A",]$samples),c(training_As,validating_As))

# Put together
training_dat &lt;- dat[dat$samples %in% c(training_As,training_Bs),]
training_categorical3_tensor &lt;- dat_categorical3_tensor[rownames(dat_categorical3_tensor) %in% c(training_As,training_Bs),]
training_labels &lt;- ifelse(training_dat$label=="A",0,1)
training_dat2 &lt;- as.matrix(training_dat[,c(3:5)]) #use this

validating_dat &lt;- dat[dat$samples %in% c(validating_As,validating_Bs),]
validating_categorical3_tensor &lt;- dat_categorical3_tensor[rownames(dat_categorical3_tensor) %in% c(validating_As,validating_Bs),]
validating_labels &lt;- ifelse(validating_dat$label=="A",0,1) 
validating_dat2 &lt;- as.matrix(validating_dat[,c(3:5)]) #use this

testing_dat &lt;- dat[dat$samples %in% c(testing_As,testing_Bs),]
testing_categorical3_tensor &lt;- dat_categorical3_tensor[rownames(dat_categorical3_tensor) %in% c(testing_As,testing_Bs),]
testing_labels &lt;- ifelse(testing_dat$label=="A",0,1)
testing_dat2 &lt;- as.matrix(testing_dat[,c(3:5)]) #use this


## Keras model

# Input layers
all_dat_input &lt;- layer_input(shape = 3, dtype = 'float32', name = 'all_dat_input')
categorical3_indices_input &lt;- layer_input(shape = 20, dtype = 'float32', name = 'categorical3_input')
input_tensor &lt;- c(all_dat_input, categorical3_indices_input)

# Output layers
all_dat_out &lt;- all_dat_input %&gt;%
  layer_dense(units = 128, activation = 'relu') %&gt;%
  layer_dropout(rate = 0.5) %&gt;%
  layer_dense(units = 128, activation = 'relu') %&gt;%
  layer_dropout(rate = 0.5)

categorical3_indices_out &lt;- categorical3_indices_input %&gt;%
  layer_dense(units = 128, activation = 'relu') %&gt;%
  layer_dropout(rate = 0.5) %&gt;%
  layer_dense(units = 128, activation = 'relu') %&gt;%
  layer_dropout(rate = 0.5)

output_tensor &lt;- layer_concatenate(c(all_dat_out, categorical3_indices_out)) %&gt;%
  layer_dense(units = 128, activation = 'relu') %&gt;%
  layer_dropout(rate = 0.5) %&gt;%
  layer_dense(units = 128, activation = 'relu') %&gt;%
  layer_dropout(rate = 0.5) %&gt;%
  layer_dense(units=1, activation="sigmoid")

model &lt;- keras_model(inputs=input_tensor, outputs=output_tensor)

# Compile
model %&gt;% compile(
  optimizer = "rmsprop",
  loss = "binary_crossentropy",
  metrics = "accuracy"
)

# Fit
history &lt;- model %&gt;% fit(
  x=list(training_dat2,training_categorical3_tensor),
  y=training_labels,
  batch_size=256,
  epochs=20,
  validation_data=list(list(validating_dat2,validating_categorical3_tensor),validating_labels)
)




# Compare with put aside testing data
results &lt;- model %&gt;% evaluate(list(testing_dat2,testing_categorical3_tensor),testing_labels)

results
$loss
[1] 0.0000001584833
$acc
[1] 1

# Using the trained network to generate predictions on new data
predictions &lt;- model %&gt;% predict(list(testing_dat2,testing_categorical3_tensor))

head(predictions[3267:3332])
[1] 0.9999921 0.9999992 0.9999999 0.9999996 0.9999999 0.9999542    
# the network correctly identifies Bs as Bs with a confidence between &gt;99%

(Remember that this is dummy data!)


I'm currently looking into feature importance to see which data inputs contributed more than others to the prediction. lime seems to be made for this!