robertness
11/10/2014 - 2:54 AM

Code for blog post on visualizing signal flow in neural network via invariant analysis

Code for blog post on visualizing signal flow in neural network via invariant analysis

#Code for blog post on visualizing signal flow in a multilayer perceptron: http://osazuwa223.github.io/2014/11/09/Visualizing-Signal-Flow-in-Neural-Network-Model/
# For source: https://github.com/osazuwa223/osazuwa223.github.com/blob/master/_posts/2014-11-09-Visualizing-Signal-Flow-in-Neural-Network-Model.md

states <- c("F.eng.has", "F.eng.none",
             "F.fr.has", "F.fr.none",
             "S.eng.has", "S.eng.none",
             "S.fr.has", "S.fr.none",
             "C.eng.has", "C.eng.none",
             "C.fr.has", "C.fr.none",
             "D.has", "D.none")
transitions <- c("F.eng->S.eng",
               "F.eng->S.fr",
               "F.fr->S.eng",
               "F.fr->S.fr",
               "S.eng->C.eng",
               "S.eng->C.fr",
               "S.fr->C.eng",
               "S.fr->C.fr",
               "C.eng->D",
               "C.fr->D",
               "D.eats")

hasPotatoBefore <- function(agent){
  out <- rep(0, length(transitions))
  if(grepl(".has", agent)){
    agent.sub <- strsplit(agent, ".has")[[1]][1]
    if(agent.sub == "D"){
      out <- grepl("D.eats", transitions)
    } else {
      passesPotato <- paste(agent.sub, "->", sep="")
      out <- grepl(passesPotato, transitions) * 1
    }
  }
  out
}
hasNoPotatoBefore <- function(agent){
  out <- rep(0, length(transitions))
  if(grepl(".none", agent)){
    agent.sub <- strsplit(agent, ".none")[[1]][1]
    getsPassedPotato <- paste("->", agent.sub, sep="")
    out <- grepl(getsPassedPotato, transitions) * 1
  }
  out
}
hasPotatoAfter <- function(agent){
  out <- rep(0, length(transitions))
  if(grepl(".has", agent)){
    agent.sub <- strsplit(agent, ".has")[[1]][1]
    getsPassedPotato <- paste("->",agent.sub , sep="")
    out <- grepl(getsPassedPotato, transitions) * 1
  }
  out
}
hasNoPotatoAfter <- function(agent){
  out <- rep(0, length(transitions))
  if(grepl(".none", agent)){
    agent.sub <- strsplit(agent, ".none")[[1]][1]
    if(agent.sub == "D"){
      out <- grepl("D.eats", transitions)
    } else{
      gaveUpPotato <- paste(agent.sub, "->", sep="")
      out <- grepl(gaveUpPotato, transitions) * 1
    }
  }
  out
}
pre <- sapply(states, hasPotatoBefore) + sapply(states, hasNoPotatoBefore)
rownames(pre) <- transitions
post <- sapply(states, hasPotatoAfter) + sapply(states, hasNoPotatoAfter)
rownames(post) <- transitions
A <- post - pre
S <- t(A)
print(S)

library(igraph)
nullmatA <- matrix(0, nrow = nrow(S), ncol = nrow(S))
rownames(nullmatA) <- rownames(S)
colnames(nullmatA) <- rownames(S)
nullmatB <- matrix(0, nrow = ncol(S), ncol = ncol(S))
rownames(nullmatB) <- colnames(S)
colnames(nullmatB) <- colnames(S)
bipartite.mat <- rbind(cbind(nullmatA, t(pre)),
      cbind(post, nullmatB))
g <- graph.adjacency(bipartite.mat)
V(g)$type <- "place"
V(g)[V(g)$name == "D.eats"]$type <- "transition"
V(g)[grepl("->", V(g)$name)]$type <- "transition"
V(g)$shape <- "circle"
V(g)[V(g)$type == "transition"]$shape <- "square"
V(g)$label <- V(g)$name
V(g)[V(g)$type == "transition"]$label = ""
layout.matrix <- matrix(c(60, 124, 60, 26, 314, 196, 52, 138, 210, 315, 318, 417, 427, 360, 206, 124, 148, 38, 194, 402, 110, 288, 282, 420, 430, 49, 0, 230, 130, 24, 106, 380, 278, 394, 318, 246, 128, 315, 393, 40, 127, 208, 318, 326, 29, 408, 186, 409, 221, 410), ncol = 2)
plot.igraph(g, layout=layout.matrix, vertex.label.dist=-.67, edge.arrow.size=.4)

getMinimalPInvariants <- function(S){
  # Get the places corresponding to minimal p invariants of an incidence matrix
  # S is an incidence matrix with rows corresponding to places and columns to transitions
  # Returns a list where each element is a pInvariant containing an array of place names
  require(MASS, quietly = T)
  nullMat <- round(Null(S), 4)
  rownames(nullMat) <- rownames(S)
  invariants <- list()
  for(j in 1:ncol(nullMat)){
    pVec <- nullMat[, j]
    valCounts <- table(pVec)
    valCounts <- valCounts[valCounts != 1]
    valCounts <- valCounts[names(valCounts) != 0]
    for(i in 1:length(valCounts)){
      val <- valCounts[i]
      invariants <- c(invariants, list(names(pVec[pVec == names(val)])))
    }
  }
  lengths <- sapply(invariants, length)
  invariants <- invariants[order(lengths)]
  if(length(invariants) > 1){
    for(i in 1:(length(invariants) - 1)){
      for(j in (i+1):length(invariants)){
        invariants[[j]] <- setdiff(invariants[[j]], invariants[[i]])
      }
    }
  }
  invariants <- invariants[which(sapply(invariants, length) > 0)]
  invariants
}

#Here is what the list of p-invariants looks like.

pInvariants <- getMinimalPInvariants(S)

#Visualizing them back on the graph:


pVertex <- lapply(pInvariants, function(p){
  V(g)[V(g)$name %in% p]
  })
plot.igraph(g, layout=layout.matrix, vertex.label.dist=-.67, 
            edge.arrow.size=.4, mark.groups=pVertex)
            
#Assign p-invariants their names

names(pInvariants) <- unlist(lapply(pInvariants, function(p){
  gsub(".has", "", p[1])
}))
pInvariants


#Start making edges for signal flow diagram
edgeMat <- NULL
# Iterate through and draw edges between invariants where there are shared places.
edgeMat <- rbind(edgeMat, do.call("rbind", combn(pInvariants, 2, function(pair){
  if(length(intersect(pair[[1]], pair[[2]])) > 0){
    list(names(pair))
  } else {
    list(NULL)
  }
})))

#Add edges based on p-invariants connected via transitions and plot
#Connect p-invariants by transitions that link their members
edgelist <- get.edgelist(g)
pre.edges <- edgelist[1:(nrow(edgelist)/2), ] #the first half of edges are outgoing from states
pre.list <- split(pre.edges[,1], as.factor(pre.edges[, 2]))
post.edges <- edgelist[((nrow(edgelist)/2) + 1):nrow(edgelist), ]
post.list <- split(post.edges[, 2], as.factor(post.edges[, 1]))
edgeMat <- rbind(edgeMat, do.call("rbind", combn(pInvariants, 2, function(pair){
  paired <- FALSE
  for(transition in transitions){
    if(any(pair[[1]] %in% pre.list[[transition]]) && any(pair[[2]] %in% post.list[[transition]])){
      paired <- TRUE
      break
    } else if(any(pair[[1]] %in% post.list[[transition]]) && any(pair[[2]] %in% pre.list[[transition]])) {
      paired <- TRUE
      break
    }   
  }
  if(paired){
    return(list(names(pair)))
  }
  else{
    return(list(NULL))
  }
})))
plot.igraph(graph.edgelist(edgeMat), vertex.label.dist=-1.5, edge.arrow.size=.4)