[读书笔记]机器学习:实用案例解析(11)
第11章 分析社交图谱
因为twitter的api方式改变了,因此按照书上的方法已经不能从twitter上获取到数据了,只能采用代码中附上的数据进行分析,而我安装的gephi无法打开图文件(.graphml)。因此本章仅讨论分析社交的思路,如果后面对web理解深入一点,再把调用api的部分补上。
“个体网络”:指在网络中直接包围在一个单独节点周围的社交关系结构;是一个网络的子集,包括一个种子(个体)和它的邻居,也就是直接与种子相连的节点、节点与种子相连的边、以及各节点之间的边。
以图的方式进行思考:无向图、有向图、带权有向图。
“入边”(edges in)与“出边”(edges out):入边可以看做是粉丝;出边可以看做是朋友或者关注的人,对于个体的研究,出边比较有意义,可以推荐个体希望关注的人。
调用API获取数据部分(暂时略)
=============================================================================
分析社交网络数据:
第一步:提取图的核心元素。
(1)“k核分析”:提取图形的2核子图:基于节点的连通性分解一个图,k核分析会得到一个k度的子图,2核分析就是由度大于等于2的节点构成的子图。选择2度的原因是,搜索方式的副作用会在网络外围产生很多单边连接的附属节点,贡献很少,需要删除。
(2)种子节点的个体网络:只考虑出边连接的节点。
第二步:对个体网络进行分析。
(1)测量图中所有节点之间的距离
(2)根据距离进行层次聚类,最开始所有节点都在一个大类里,最后分到每个节点单独一个类,画出聚类树状图。
library(igraph) source('ML_for_Hackers/11-SNA/01_google_sg.R') #载入数据 user <- 'johnmyleswhite' user.net <- read.graph(paste("ML_for_Hackers/11-SNA/data/", user, "/", user, "_net.graphml", sep = ""), format = "graphml") user.net <- set.vertex.attribute(user.net, "Label", value = get.vertex.attribute(user.net, "name")) #2核分析得到一个2度子图 user.cores <- graph.coreness(user.net, mode = "in") user.clean <- subgraph(user.net, which(user.cores>1)) #得到种子节点的个体网络子图 user.ego <- subgraph(user.net, c(1, neighbors(user.net, user, mode = "out"))) #测量图中所有 节点之间的距离 user.sp <- shortest.paths(user.ego) #dist()函数由生成距离矩阵(主要目的是将user.sp的格式转换为hclust()可用的格式); #hclust()函数进行聚类,返回全部聚类信息的对象 user.hc <- hclust(dist(user.sp)) #画出聚类树状图 plot(user.hc)
使用Gephi可视化聚类网络(略)
建立“感兴趣的人”引擎:
基本原理:朋友的朋友是朋友(因为所谓的“敌人”在社交网络的好友功能无法体现,因此不考虑“敌人”的情况)
#设置研究对象 user <- 'drewconway' user.graph <- read.graph(paste("ML_for_Hackers/11-SNA/data/", user, "/", user, "_net.graphml", sep = ""), format = "graphml") #获取种子的所有朋友的用户名.V()可以返回图的某一特定属性,这里即是name friends <- V(user.graph)$name[neighbors(user.graph, user, mode = "out")] #get.edgelist()函数生成图的完整边列表 user.el <- get.edgelist(user.graph) #检查矩阵中每一行是否包含了种子用户没有关注的“朋友的朋友” #ifelse()函数的逻辑:首先判断是否是种子用户或者第一个元素(起点)不是种子用户的朋友,两者之一为真就跳过这一行; # 再看当前行的第二个元素(目标节点)是否是种子用户的朋友,若为真则跳过这一行 non.friends <- sapply(1:nrow(user.el), function(i) ifelse(any(user.el[i, ]==user | !user.el[i, 1] %in% friends) | user.el[i, 2] %in% friends, FALSE, TRUE)) #提取合适的行,并建立包含名字出现次数的表格 non.friends.el <- user.el[which(non.friends==TRUE), ] friends.count <- table(non.friends.el[, 2]) #找到这份数据中出现最多的用户,用table()函数创建的向量来创建一个数据框; #并使用归一化方法计算最应该被关注的推荐用户,即计算种子用户的朋友关注候选推荐用户的百分比 #最后根据百分比递减排序,查看前10条数据 friends.followers <- data.frame(list(Twitter.Users = names(friends.count), Friends.Following = as.numeric(friends.count)), stringsAsFactors = FALSE) friends.followers$Friends.Norm <- friends.followers$Friends.Following/length(friends) friends.followers <- friends.followers[with(friends.followers, order(-Friends.Norm)), ] friends.followers[1:10, ]
但是这个推荐结果里面有很多用户是种子已经关注的人了。另一种思路是,除了推荐“朋友的朋友外”,也可以推荐某个已知维度上和种子用户类似的朋友的朋友,也就是通常所说的圈子。
#推荐某个圈子中的“朋友的朋友” #结果的friends.partitions矩阵中,第一列是分割编号,第二列是用户名 user.ego <- read.graph(paste("ML_for_Hackers/11-SNA/data/", user, "/", user, "_ego.graphml", sep = ""), format = "graphml") friends.partitions <- cbind(V(user.ego)$HC8, V(user.ego)$name) head(friends.partitions)
#这个函数的目的是,输入分割编号(不同的编号代表不同的圈子),输出这个分割编号里被种子用户的朋友关注最多的用户,也就是要给种子用户推荐的那个用户 partition.follows <- function(i) { friends.in <- friends.partitions[which(friends.partitions[, 1] == i), 2] partition.non.follow <- non.friends.el[which(!is.na(match(non.friends.el[, 1], friends.in))), ] if (nrow(partition.non.follow) < 2) { return(c(i, NA)) } else { partition.favorite <- table(partition.non.follow[, 2]) partition.favorite <- partition.favorite[order(-partition.favorite)] return(c(i, names(partition.favorite)[1])) } } partition.recs <- t(sapply(unique(friends.partitions[, 1]), partition.follows)) partition.recs <- partition.recs[!is.na(partition.recs[, 2]) & !duplicated(partition.recs[, 2]), ]
结果如图