摘要:GraphSAGE
鹊杖,tensorflow
悴灵,tensorflow_model_server
,tensorboard
骂蓖,saved_model_cli
GraphSAGE實戰(zhàn)目標
GraphSAGE的目標是對節(jié)點的鄰居采樣积瞒,從而避免每次計算都需要拿到全部節(jié)點的鄰接矩陣,因此可以將訓(xùn)練好的模型直接用于新節(jié)點預(yù)測登下,GraphSAGE的實戰(zhàn)目標就是對新的在訓(xùn)練數(shù)據(jù)中沒有出現(xiàn)過的中心節(jié)點赡鲜,基于該節(jié)點自身的特征和鄰居特征,預(yù)測該節(jié)點的任務(wù)庐船,比如預(yù)測該節(jié)點的分類银酬。
GraphSAGE數(shù)據(jù)鏈路分析
這塊參考另一篇博客文章http://www.reibang.com/p/073e39c2d81a,簡單而言就是模型層實際上就是學(xué)習(xí)到了每一層卷積的W筐钟,以及最后一層全連接的W揩瞪,輸入部分輸入節(jié)點自身
,節(jié)點1跳
篓冲,節(jié)點2跳
的特征向量
李破,整體來看還是比較清晰簡單的。
數(shù)據(jù)準備預(yù)處理
數(shù)據(jù)采用的cora數(shù)據(jù)壹将,將全部數(shù)據(jù)分為訓(xùn)練
嗤攻,驗證
,預(yù)測
诽俯,三個部分獨立存儲妇菱,其中驗證集作為早停,預(yù)測集用來評價模型暴区,注意雖然三個數(shù)據(jù)集的節(jié)點索引互不重合闯团,但是在采樣過程中訓(xùn)練集的節(jié)點可以采樣到他的屬于驗證集的鄰居,只是拿到鄰居的特征向量仙粱,不知道節(jié)點的y值房交。具體實現(xiàn)如下:
import os
import pickle
import numpy as np
import scipy.sparse as sp
BASIC_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
def data_split():
names = ['x', 'y', 'tx', 'ty', 'allx', 'ally', 'graph']
objects = []
for i in range(len(names)):
with open(os.path.join(BASIC_PATH, "./data/ind.cora.{}".format(names[i])), 'rb') as f:
objects.append(pickle.load(f, encoding='latin1'))
x, y, tx, ty, allx, ally, graph = tuple(objects)
test_idx_reorder = [int(x.strip()) for x in open(os.path.join(BASIC_PATH, "./data/ind.cora.test.index"), "r").readlines()]
test_idx_range = np.sort(test_idx_reorder)
# 測試索引位置修正
features = sp.vstack((allx, tx)).tolil()
features[test_idx_reorder, :] = features[test_idx_range, :]
labels = np.vstack((ally, ty))
labels[test_idx_reorder, :] = labels[test_idx_range, :]
# 訓(xùn)練[:1000],驗證[1000:1708],測試[1708:]
train_nodes = list(range(1000))
train_y = labels[train_nodes]
val_nodes = list(range(1000, 1708))
val_y = labels[val_nodes]
test_nodes = list(range(1708, 2708))
test_y = labels[test_nodes]
return train_nodes, train_y, val_nodes, val_y, test_nodes, test_y, graph, features
def sample(nodes, neighbour_list, k=2, num_supports=None):
if num_supports is None:
num_supports = [10, 25]
assert len(num_supports) == k, "num_supports長度必須和k階相等"
layer_neighbours = {}
for i in range(k):
neighbours = []
num_support = num_supports[i]
for node in nodes:
one_neighbour = neighbour_list[node]
if len(one_neighbour) >= num_support:
neighbours.append(np.random.choice(neighbour_list[node], num_support, replace=False).tolist())
else:
neighbours.append(np.random.choice(neighbour_list[node], num_support, replace=True).tolist())
layer_neighbours[k - i] = neighbours
nodes = sum(neighbours, [])
return layer_neighbours
def get_nodes_features(nodes, features_embedding, std=True):
embedding = features_embedding[nodes]
if std:
embedding = embedding / embedding.sum(axis=1)
return embedding
if __name__ == '__main__':
train_nodes, train_y, val_nodes, val_y, test_nodes, test_y, graph, features = data_split()
pickle.dump((train_nodes, train_y), open(os.path.join(BASIC_PATH, "./data/train.pkl"), "wb"))
pickle.dump((val_nodes, val_y), open(os.path.join(BASIC_PATH, "./data/val.pkl"), "wb"))
pickle.dump((test_nodes, test_y), open(os.path.join(BASIC_PATH, "./data/test.pkl"), "wb"))
pickle.dump(graph, open(os.path.join(BASIC_PATH, "./data/graph.pkl"), "wb"))
pickle.dump(features, open(os.path.join(BASIC_PATH, "./data/features.pkl"), "wb"))
以上代碼將數(shù)據(jù)集的前1000作為訓(xùn)練,后1000作為測試伐割,中間作為驗證候味,保證節(jié)點索引順序和節(jié)點特征向量順序一致,然后定義了sample
和get_nodes_features
函數(shù)隔心,sample目的是輸入一組節(jié)點白群,得到他的1跳,2跳節(jié)點济炎,get_nodes_features目的是將節(jié)點索引轉(zhuǎn)化為節(jié)點特征矩陣川抡。
模型部分
模型部分整體參考了https://github.com/williamleif/GraphSAGE,對其進行了簡化,具體表現(xiàn)為
-
固定卷積層=2
:大部分情況下卷積層就是2崖堤,只要這個一確定侍咱,模型層的代碼相當好寫 -
將采樣部分從模型層解耦出來
:源代碼中作者將采樣部分寫入模型層在tensor中計算,好處是這樣只需要傳遞節(jié)點索引密幔,可以設(shè)置更靈活的卷積層在模型層做采樣處理楔脯,壞處是他把全局的鄰居列表傳入了模型做初始化,因此在預(yù)測階段新的鄰居列表無法傳入胯甩。 -
固定聚合方式為MEAN
:先以MEAN跑通流程昧廷,其他聚合模式以后再研究。
同時增加了一些新的要素偎箫,具體如下:
-
早停
:使用驗證集早停木柬,方式過擬合。 -
學(xué)習(xí)率衰減
:學(xué)習(xí)率隨著訓(xùn)練步長衰減淹办。 -
檢查點和pb文件保存
:增加工程部分眉枕,能真正實現(xiàn)模型部署。
代碼如下怜森,采用標準的tensorflow 1.x風(fēng)格
import numpy as np
import tensorflow as tf
def glorot(shape, name=None):
init_range = np.sqrt(6.0 / (shape[0] + shape[1]))
initial = tf.random_uniform(shape, minval=-init_range, maxval=init_range, dtype=tf.float32)
return tf.Variable(initial, name=name)
def zeros(shape, name=None):
initial = tf.zeros(shape, dtype=tf.float32)
return tf.Variable(initial, name=name)
class GraphSageGCN(object):
def __init__(self, num_class, feature_size, num_supports_1=10, num_supports_2=10, dim_1=128, dim_2=128,
learning_rate=0.01,
weight_decay=0.01, decay_learning_rate=0.9, concat=True):
# 中心節(jié)點
self.input_self = tf.placeholder(tf.float32, [None, feature_size], name="input_self")
# 1跳
self.input_neigh_1 = tf.placeholder(tf.float32, [None, num_supports_1, feature_size], name="input_neigh_1")
# 2跳
self.input_neigh_2 = tf.placeholder(tf.float32, [None, num_supports_1, num_supports_2, feature_size], name="input_neigh_2")
self.input_y = tf.placeholder(tf.int64, [None, num_class])
self.dropout_keep_prob = tf.placeholder(tf.float32, name="dropout_keep_prob")
self.global_step = tf.Variable(0, name="global_step", trainable=False)
# 第1階卷積
with tf.name_scope('convolution_1'):
# 權(quán)重共享
self_weight_1 = glorot([feature_size, dim_1], name="self_weight")
neigh_weight_1 = glorot([feature_size, dim_1], name="neigh_weight")
# 1層的1跳聚合
neigh_vec_1_1 = tf.nn.dropout(self.input_neigh_1, self.dropout_keep_prob)
self_vec_1_1 = tf.nn.dropout(self.input_self, self.dropout_keep_prob)
neigh_means_1_1 = tf.reduce_mean(neigh_vec_1_1, axis=1)
from_neighs_1_1 = tf.matmul(neigh_means_1_1, neigh_weight_1)
from_self_1_1 = tf.matmul(self_vec_1_1, self_weight_1)
output_1_1 = tf.nn.relu(tf.concat([from_self_1_1, from_neighs_1_1], axis=1))
# 1層的2跳聚合
neigh_vec_1_2 = tf.nn.dropout(self.input_neigh_2, self.dropout_keep_prob)
self_vec_1_2 = tf.nn.dropout(self.input_neigh_1, self.dropout_keep_prob)
# [None, feature_size] => [None, None, feature_size]
neigh_means_1_2 = tf.reduce_mean(tf.reshape(neigh_vec_1_2, [-1, num_supports_2, feature_size]), axis=1)
from_neighs_1_2 = tf.matmul(neigh_means_1_2, neigh_weight_1)
from_self_1_2 = tf.matmul(tf.reshape(self_vec_1_2, [-1, feature_size]), self_weight_1)
output_1_2 = tf.nn.relu(tf.concat([from_self_1_2, from_neighs_1_2], axis=1))
# 第二層卷積
with tf.name_scope("convolution_2"):
self_weight_2 = glorot([dim_1 * 2, dim_2], name="self_weight")
neigh_weight_2 = glorot([dim_1 * 2, dim_2], name="neigh_weight")
neigh_vec_2_1 = tf.nn.dropout(output_1_2, self.dropout_keep_prob)
self_vec_2_1 = tf.nn.dropout(output_1_1, self.dropout_keep_prob)
# [None * num_supports[0], dim_1 * 2] => [None, num_supports[0], dim_1 * 2]
neigh_means_2_1 = tf.reduce_mean(tf.reshape(neigh_vec_2_1, [-1, num_supports_1, dim_1 * 2]), axis=1)
from_neighs_2_1 = tf.matmul(neigh_means_2_1, neigh_weight_2)
from_self_2_1 = tf.matmul(self_vec_2_1, self_weight_2)
output_2_1 = tf.concat([from_self_2_1, from_neighs_2_1], axis=1)
# dense
with tf.name_scope("dense"):
dense_weight = tf.get_variable('weights', shape=(dim_2 * 2, num_class),
dtype=tf.float32,
initializer=tf.contrib.layers.xavier_initializer())
dense_bias = zeros([num_class], name='bias')
dense_input = tf.nn.dropout(output_2_1, self.dropout_keep_prob)
output = tf.matmul(dense_input, dense_weight) + dense_bias
# softmax
with tf.name_scope("softmax"):
self.probs = tf.nn.softmax(output, dim=1, name="probs")
self.accuracy = tf.reduce_mean(
tf.cast(tf.equal(tf.arg_max(self.probs, 1), tf.arg_max(self.input_y, 1)), dtype=tf.float32))
# loss
with tf.name_scope('loss'):
# 交叉熵
self.loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(logits=output, labels=self.input_y))
# 正則
self.loss += weight_decay * (tf.nn.l2_loss(self_weight_1) + tf.nn.l2_loss(neigh_weight_1) + tf.nn.l2_loss(
self_weight_2) + tf.nn.l2_loss(neigh_weight_2) + tf.nn.l2_loss(dense_weight))
# optimizer
with tf.name_scope("optimizer"):
if decay_learning_rate:
learning_rate = tf.train.exponential_decay(learning_rate, self.global_step, 100, decay_learning_rate)
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)
self.train_step = optimizer.minimize(self.loss, global_step=self.global_step)
with tf.name_scope("summaries"):
tf.summary.scalar("loss", self.loss)
tf.summary.scalar("accuracy", self.accuracy)
self.summary_op = tf.summary.merge_all()
可見模型的訓(xùn)練參數(shù)就是兩層卷積的W和全連接的W速挑,其中設(shè)置了3個輸入(dropout除外),分別是中心節(jié)點特征矩陣
副硅,一跳節(jié)點特征矩陣
姥宝,2跳節(jié)點特征矩陣
,維度分別是2
,3
,4
恐疲,全部在model層中寫死腊满。保證這三個輸入的第一維度值大小是一致的都是batch_size,否則在tensorflow_model_server中無法部署流纹。
訓(xùn)練部分
訓(xùn)練部分主要包括兩段內(nèi)容:
-
get_batch
:批次函數(shù)糜烹,實現(xiàn)GraphSAGE的小批量訓(xùn)練违诗,采用的按照批次滑動采樣漱凝,再重復(fù)樣本,最終返回的是迭代器诸迟,內(nèi)容包括中心節(jié)點索引茸炒,1條索引,2條索引阵苇。 -
train_main
:訓(xùn)練函數(shù)壁公,包括模型初始化,將采樣節(jié)點轉(zhuǎn)化為特征向量绅项,記錄訓(xùn)練過程和驗證過程紊册,早停的實現(xiàn)。其中早停是連續(xù)5次沒有出現(xiàn)新的loss低點就停止,每次驗證集loss下降就保存一次檢查點囊陡,最終取最新的檢查點芳绩,檢查點只保存1份。
import sys
import os
import pickle
import shutil
import random
import time
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
DATA_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
ROOT_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import numpy as np
import tensorflow as tf
from tensorflow.python.saved_model import tag_constants
from model import GraphSageGCN
from utils.config import get_string
from preprocessing import sample, get_nodes_features
(train_nodes, train_y) = pickle.load(open(os.path.join(DATA_PATH, get_string("train_data_path")), "rb"))
(val_nodes, val_y) = pickle.load(open(os.path.join(DATA_PATH, get_string("val_data_path")), "rb"))
(test_nodes, test_y) = pickle.load(open(os.path.join(DATA_PATH, get_string("test_data_path")), "rb"))
neighbour_list = pickle.load(open(os.path.join(DATA_PATH, get_string("neighbour_data_path")), "rb"))
nodes_features = pickle.load(open(os.path.join(DATA_PATH, get_string("feature_data_path")), "rb"))
features_size = nodes_features.shape[1]
def get_batch(epoches, batch_size, nodes, labels, neighbours, features, layer1_supports=10, layer2_supports=5):
for epoch in range(epoches):
tmp = list(zip(nodes, labels))
random.shuffle(tmp)
nodes, labels = zip(*tmp)
for batch in range(0, len(nodes), batch_size):
if batch + batch_size < len(nodes):
batch_nodes = nodes[batch: (batch + batch_size)]
batch_labels = labels[batch: (batch + batch_size)]
else:
batch_nodes = nodes[batch: len(nodes)]
batch_labels = labels[batch: len(nodes)]
# 得到訓(xùn)練集的1跳2跳
layer_neighbours = sample(batch_nodes, neighbours, num_supports=[layer2_supports, layer1_supports])
# 所有節(jié)點的embedding
input_x = get_nodes_features(list(batch_nodes), features)
input_x_1 = get_nodes_features(sum(layer_neighbours[2], []), features)
input_x_2 = get_nodes_features(sum(layer_neighbours[1], []), features)
yield [epoch, input_x, input_x_1, input_x_2, batch_labels]
def train_main():
tf.reset_default_graph()
model = GraphSageGCN(num_class=7, feature_size=1433,
num_supports_1=int(get_string("layer2_supports")),
num_supports_2=int(get_string("layer1_supports")),
decay_learning_rate=float(get_string("decay_learning_rate")),
learning_rate=float(get_string("learning_rate")),
weight_decay=float(get_string("weight_decay")))
saver = tf.train.Saver(tf.global_variables(), max_to_keep=1)
with tf.Session() as sess:
init_op = tf.group(tf.global_variables_initializer())
sess.run(init_op)
shutil.rmtree(os.path.join(ROOT_PATH, "./summary"), ignore_errors=True)
writer = tf.summary.FileWriter(os.path.join(ROOT_PATH, "./summary"), sess.graph)
batches = get_batch(int(get_string("epoches")), int(get_string("batch_size")), train_nodes, train_y,
neighbour_list, nodes_features, layer1_supports=int(get_string("layer1_supports")),
layer2_supports=int(get_string("layer2_supports")))
# 驗證數(shù)據(jù)
layer_neighbours = sample(val_nodes, neighbour_list,
num_supports=[int(get_string("layer2_supports")), int(get_string("layer1_supports"))])
val_input_x = get_nodes_features(val_nodes, nodes_features)
val_input_x_1 = get_nodes_features(sum(layer_neighbours[2], []), nodes_features)
val_input_x_2 = get_nodes_features(sum(layer_neighbours[1], []), nodes_features)
val_feed_dict = {model.input_self: val_input_x,
model.input_neigh_1: val_input_x_1.A.reshape(-1, int(get_string("layer1_supports")),
features_size),
model.input_neigh_2: val_input_x_2.A.reshape(-1, int(get_string("layer1_supports")),
int(get_string("layer2_supports")),
features_size),
model.input_y: val_y,
model.dropout_keep_prob: 1}
val_loss_list = []
for batch in batches:
epoch, input_x, input_x_1, input_x_2, input_y = batch
feed_dict = {model.input_self: input_x,
model.input_neigh_1: input_x_1.A.reshape(-1, int(get_string("layer1_supports")),
features_size),
model.input_neigh_2: input_x_2.A.reshape(-1, int(get_string("layer1_supports")),
int(get_string("layer2_supports")), features_size),
model.input_y: input_y,
model.dropout_keep_prob: float(get_string("dropout_keep_prob"))}
_, step, loss_val, acc_val, merged = sess.run(
[model.train_step, model.global_step, model.loss, model.accuracy, model.summary_op],
feed_dict=feed_dict)
writer.add_summary(merged, step)
if step % 5 == 0:
print("epoch:", epoch + 1, "step:", step, "loss:", loss_val, "accuracy:", acc_val)
if step % 20 == 0:
loss_val, acc_val = sess.run([model.loss, model.accuracy], feed_dict=val_feed_dict)
print("{:-^30}".format("evaluation"))
print("[evaluation]", "loss:", loss_val, "accuracy:", acc_val)
# 計算當前l(fā)oss相比之前的最有l(wèi)oss下降多少
diff = (loss_val - min(val_loss_list)) if len(val_loss_list) else 0
val_loss_list.append(loss_val)
print("本輪loss比之前最小loss{}:{}, 當前最小loss: {}"
.format("上升" if diff > 0 else "下降", abs(diff), min(val_loss_list)))
if diff < 0:
saver.save(sess, os.path.join(ROOT_PATH, get_string("checkpoint_path")))
print("[save checkpoint]")
print("-" * 40)
if early_stop(val_loss_list, windows=int(get_string("early_stop_windows"))):
print("{:-^30}".format("early stop!"))
break
def early_stop(loss_list, windows=5):
if len(loss_list) <= windows:
return False
latest_loss = loss_list[-windows:]
previous_loss = loss_list[:-windows]
min_previous_loss = min(previous_loss)
min_latest_loss = min(latest_loss)
if min_latest_loss > min_previous_loss:
return True
return False
運行之后打印的輸出如下
epoch: 12 step: 185 loss: 0.5722251 accuracy: 0.953125
epoch: 12 step: 190 loss: 0.52046406 accuracy: 0.984375
epoch: 13 step: 195 loss: 0.57312083 accuracy: 0.9375
epoch: 13 step: 200 loss: 0.482001 accuracy: 1.0
----------evaluation----------
[evaluation] loss: 0.85649866 accuracy: 0.8629944
本輪loss比之前最小loss上升:0.038332998752593994, 當前最小loss: 0.81816565990448
----------------------------------------
epoch: 13 step: 205 loss: 0.56127137 accuracy: 0.953125
epoch: 14 step: 210 loss: 0.5034015 accuracy: 0.984375
epoch: 14 step: 215 loss: 0.4905039 accuracy: 0.984375
epoch: 14 step: 220 loss: 0.5316473 accuracy: 0.96875
----------evaluation----------
[evaluation] loss: 0.8504291 accuracy: 0.8615819
本輪loss比之前最小loss上升:0.03226345777511597, 當前最小loss: 0.81816565990448
----------------------------------------
---------early stop!----------
最終訓(xùn)練集準確率能達到1撞反,驗證集準確率有0.86妥色。
測試部分
測試部分讀取最新的檢查點,使用get_tensor_by_name
拿到輸入tensor和輸出tensor遏片,用新的test數(shù)據(jù)灌入輸入tensor拿到預(yù)測tensor嘹害。
def test_main():
layer_neighbours = sample(test_nodes, neighbour_list,
num_supports=[int(get_string("layer2_supports")), int(get_string("layer1_supports"))])
test_input_x = get_nodes_features(test_nodes, nodes_features)
test_input_x_1 = get_nodes_features(sum(layer_neighbours[2], []), nodes_features)
test_input_x_2 = get_nodes_features(sum(layer_neighbours[1], []), nodes_features)
tf.reset_default_graph()
with tf.Session() as sess:
last_ckpt = tf.train.latest_checkpoint(
os.path.join(ROOT_PATH, "/".join(get_string("checkpoint_path").split("/")[:-1])))
print("讀取ckpt: {}".format(last_ckpt))
saver = tf.train.import_meta_graph("{}.meta".format(last_ckpt))
saver.restore(sess, last_ckpt)
graph = tf.get_default_graph()
# get tensor
input_self = graph.get_tensor_by_name("input_self:0")
input_neigh_1 = graph.get_tensor_by_name("input_neigh_1:0")
input_neigh_2 = graph.get_tensor_by_name("input_neigh_2:0")
dropout_keep_prob = graph.get_tensor_by_name("dropout_keep_prob:0")
pred = graph.get_tensor_by_name("softmax/probs:0")
prediction = sess.run(pred, feed_dict={input_self: test_input_x,
input_neigh_1: test_input_x_1.A.reshape(-1, int(get_string("layer1_supports")), features_size),
input_neigh_2: test_input_x_2.A.reshape(-1, int(get_string("layer1_supports")), int(get_string("layer2_supports")), features_size),
dropout_keep_prob: 1.0})
hit = np.equal(np.argmax(prediction, axis=1), np.argmax(test_y, axis=1))
accuracy = hit.sum() / len(hit)
print("[test]:", accuracy)
執(zhí)行打印的輸入如下
讀取ckpt: /home/myproject/GRAPHSAGE_CORA/./ckpt/ckpt
[test]: 0.834
測試集準備率有0.834
tensorboard查看訓(xùn)練標量
tensorboard簡單查看以下訓(xùn)練過程,用以做ppt使用吮便,切到summary目錄下
tensorboard --logdir `pwd`
打開127.0.0.1:6006
accuracy持續(xù)上升笔呀,loss持續(xù)下降
模型保存為pb
模型保存為pb也是拿的最新的檢查點文件,都是tensorflow的標準代碼
def save_pb():
# 模型保存
pb_num = str(int(time.time()))
pb_path = os.path.join(ROOT_PATH, get_string("pb_path"), pb_num)
shutil.rmtree(pb_path, ignore_errors=True)
tf.reset_default_graph()
with tf.Session() as sess:
last_ckpt = tf.train.latest_checkpoint(
os.path.join(ROOT_PATH, "/".join(get_string("checkpoint_path").split("/")[:-1])))
print("讀取ckpt: {}".format(last_ckpt))
saver = tf.train.import_meta_graph("{}.meta".format(last_ckpt))
saver.restore(sess, last_ckpt)
graph = tf.get_default_graph()
# get tensor
input_self = graph.get_tensor_by_name("input_self:0")
input_neigh_1 = graph.get_tensor_by_name("input_neigh_1:0")
input_neigh_2 = graph.get_tensor_by_name("input_neigh_2:0")
dropout_keep_prob = graph.get_tensor_by_name("dropout_keep_prob:0")
pred = graph.get_tensor_by_name("softmax/probs:0")
builder = tf.saved_model.builder.SavedModelBuilder(pb_path)
inputs = {'input_self': tf.saved_model.utils.build_tensor_info(input_self),
'input_neigh_1': tf.saved_model.utils.build_tensor_info(input_neigh_1),
'input_neigh_2': tf.saved_model.utils.build_tensor_info(input_neigh_2),
'dropout_keep_prob': tf.saved_model.utils.build_tensor_info(dropout_keep_prob),
}
outputs = {'output': tf.saved_model.utils.build_tensor_info(pred)}
signature = tf.saved_model.signature_def_utils.build_signature_def(
inputs=inputs,
outputs=outputs,
method_name=tf.saved_model.signature_constants.PREDICT_METHOD_NAME)
builder.add_meta_graph_and_variables(sess, [tag_constants.SERVING], {'my_signature': signature})
builder.save()
print("pb文件保存完成:", pb_num)
執(zhí)行完畢后tfserving目錄下會有多個時間戳的模型文件
root@ubuntu:~/myproject/GRAPHSAGE_CORA/tfserving# tree
.
└── 1641799963
├── saved_model.pb
└── variables
├── variables.data-00000-of-00001
└── variables.index
模型server API服務(wù)
先使用saved_model_cli
起一個測試服務(wù)髓需,先查看一下pb的參數(shù)信息
root@ubuntu:~/myproject/GRAPHSAGE_CORA/tfserving# saved_model_cli show --all --dir 1641799963
輸出如下凿可,可以看到4個input信息以及維度,一個output信息
signature_def['my_signature']:
The given SavedModel SignatureDef contains the following input(s):
inputs['dropout_keep_prob'] tensor_info:
dtype: DT_FLOAT
shape: unknown_rank
name: dropout_keep_prob:0
inputs['input_neigh_1'] tensor_info:
dtype: DT_FLOAT
shape: (-1, 10, 1433)
name: input_neigh_1:0
inputs['input_neigh_2'] tensor_info:
dtype: DT_FLOAT
shape: (-1, 10, 10, 1433)
name: input_neigh_2:0
inputs['input_self'] tensor_info:
dtype: DT_FLOAT
shape: (-1, 1433)
name: input_self:0
The given SavedModel SignatureDef contains the following output(s):
outputs['output'] tensor_info:
dtype: DT_FLOAT
shape: (-1, 7)
name: softmax/probs:0
Method name is: tensorflow/serving/predict
可以看到需要輸入的參數(shù)和維度授账,類型要求枯跑,下一步啟動docker的serving服務(wù)
docker run --rm \
-p 13713:8501 \
-v /home/myproject/GRAPHSAGE_CORA/tfserving/:/models/graphsage_cora/ \
-e MODEL_NAME=graphsage_cora \
--name graphsage_api \
tensorflow/serving
注意MODEL_NAME
和掛載到容器內(nèi)的models下的目錄名稱保持一致,寫一個Python腳本測試一下api調(diào)用
import os
import pickle
import sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import requests
from preprocessing import sample, get_nodes_features
from utils.config import get_string
DATA_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
neighbour_list = pickle.load(open(os.path.join(DATA_PATH, get_string("neighbour_data_path")), "rb"))
nodes_features = pickle.load(open(os.path.join(DATA_PATH, get_string("feature_data_path")), "rb"))
if __name__ == '__main__':
nodes = [2555]
layer_neighbours = sample(nodes, neighbour_list,
num_supports=[int(get_string("layer2_supports")), int(get_string("layer1_supports"))])
test_input_x = get_nodes_features(nodes, nodes_features)
test_input_x_1 = get_nodes_features(sum(layer_neighbours[2], []), nodes_features)
test_input_x_2 = get_nodes_features(sum(layer_neighbours[1], []), nodes_features)
res = requests.post("http://127.0.0.1:13713/v1/models/graphsage_cora:predict", json={"instances": [{
"input_self": test_input_x.A[0].tolist(),
"input_neigh_1": test_input_x_1.A.reshape(-1, 10, 1433)[0].tolist(),
"input_neigh_2": test_input_x_2.A.reshape(-1, 10, 10, 1433)[0].tolist(),
"dropout_keep_prob": 1.0
}], "signature_name": "my_signature"})
print(res.json())
輸出如下
{'predictions': [[0.0151401442, 0.00185001828, 0.000755791727, 0.97733295, 0.00436462974, 0.000487773854, 6.87406064e-05]]}
結(jié)果是第2555個節(jié)點預(yù)測是第4分類白热。