程世東老師TensorFlow實戰——個性化推薦,程式碼學習筆記之②模型訓練與測試
阿新 • • 發佈:2018-12-04
個性化推薦第二部分:模型訓練
程式碼來自於知乎:https://zhuanlan.zhihu.com/p/32078473
/程式碼地址https://github.com/chengstone/movie_recommender/blob/master/movie_recommender.ipynb
前面資料預處理已經將訓練好的資料儲存在proprecess.p檔案中,這裡直接引用(從本地讀取)即可,不用每次都跑一遍模型預處理過程
具體描述過程請參加原博(上面連結)
import tensorflow as tf import os import pickle import matplotlib.pyplot as plt import time import datetime def save_params(params): """ Save parameters to file """ pickle.dump(params, open('params.p', 'wb')) def load_params(): """ Load parameters from file """ return pickle.load(open('params.p', mode='rb')) #從本地讀取資料 title_count, title_set, genres2int, features, targets_values, ratings, users, movies, data, movies_orig, users_orig = pickle.load(open('preprocess.p', mode='rb')) #嵌入矩陣的維度:一個單詞或其他變數的特徵表示 embed_dim = 32 #features為 # [ [1, 1193, 0, ..., 10,list([ title]),list([ genres])], # [2, 1193, 1, ..., 16,list([ ]),list([ ])], # [12, 1193, 1, ..., 12,list([ ]),list([ ])], # ..., # [5938, 2909, 1, ..., 1,list([ ]),list([ ])] # ] #使用者ID個數 uid_max = max(features.take(0,1)) + 1 # 6040 #features.take(0,1)得到userid的全部列,由於從0開始編號,則max取最大值再加1可以得到使用者id個數 #ndarray.take(indices, axis=None, out=None, mode='raise')從軸axis上獲取陣列中的元素,並以一維陣列或者矩陣返回 #按axis選擇處於indices位置上的值 #axis用於選擇值的軸,0為橫軸,1為縱向選 #如features.take(0,0)就會選擇橫向第一條資料,(1,0)會選擇橫向第二條資料 #性別個數 gender_max = max(features.take(2,1)) + 1 # 1 + 1 = 2 #年齡類別個數 age_max = max(features.take(3,1)) + 1 # 6 + 1 = 7 #職業個數 job_max = max(features.take(4,1)) + 1# 20 + 1 = 21 #電影ID個數 movie_id_max = max(features.take(1,1)) + 1 # 3952 #電影型別個數 movie_categories_max = max(genres2int.values()) + 1 # 18 + 1 = 19 #電影名單詞個數 movie_title_max = len(title_set) # 5216 # title_set是由空格分開的電影單詞字串構成的列表(set表) #對電影型別嵌入向量做加和操作的標誌,後面呼叫combiner來使用作為引數 combiner = "sum" #電影名長度 sentences_size = title_count # title_count=15重新命名,一個電影title欄位的長度,不夠會補 #文字卷積滑動視窗,分別滑動2, 3, 4, 5個單詞 window_sizes = {2, 3, 4, 5} #文字卷積核數量 filter_num = 8 #電影ID轉下標的字典,注意資料集中電影ID跟下標不一致,比如第5行的資料電影ID不一定是5 movieid2idx = {val[0]:i for i, val in enumerate(movies.values)} #格式為{movieid :i} #1:0,2:1,... #超參----------------------------------------------- # Number of Epochs num_epochs = 5 # Batch Size batch_size = 256 dropout_keep = 0.5 # Learning Rate learning_rate = 0.0001 # Show stats for every n number of batches show_every_n_batches = 20 #後面要儲存模型的地址引數賦給變數save_dir save_dir = './save' #輸入----------------------------------------------- #定義輸入的佔位符 def get_inputs(): #tf.placeholder(dtype, shape=None, name=None)此函式可以理解為形參,用於定義過程,在執行的時候再賦具體的值 #dtype:資料型別 #shape:資料形狀。預設是None,[行數,列數] #name:名稱 uid = tf.placeholder(tf.int32, [None, 1], name="uid") #這裡一行代表一個使用者的id,是batch×1,每一行是一個列表 user_gender = tf.placeholder(tf.int32, [None, 1], name="user_gender") user_age = tf.placeholder(tf.int32, [None, 1], name="user_age") user_job = tf.placeholder(tf.int32, [None, 1], name="user_job") movie_id = tf.placeholder(tf.int32, [None, 1], name="movie_id") movie_categories = tf.placeholder(tf.int32, [None, 18], name="movie_categories") movie_titles = tf.placeholder(tf.int32, [None, 15], name="movie_titles") targets = tf.placeholder(tf.int32, [None, 1], name="targets") LearningRate = tf.placeholder(tf.float32, name = "LearningRate") dropout_keep_prob = tf.placeholder(tf.float32, name = "dropout_keep_prob") return uid, user_gender, user_age, user_job, movie_id, movie_categories, movie_titles, targets, LearningRate, dropout_keep_prob #構建神經網路--------------------------------------- #定義User的嵌入矩陣,完成原始矩陣經過嵌入層後得到的輸出 def get_user_embedding(uid, user_gender, user_age, user_job): #見作者結構圖,使用者嵌入矩陣輸入4個 with tf.name_scope("user_embedding"): #用於後面tensorboard視覺化圖層關係 #使用者id uid_embed_matrix = tf.Variable(tf.random_uniform([uid_max, embed_dim], -1, 1), name = "uid_embed_matrix") #tf.Variable(initializer,name):initializer是初始化引數,可以有tf.random_normal,tf.constant等,name是變數的名字 #tf.random_uniform(shape, minval=0,maxval=None,dtype=tf.float32) 從均勻分佈中輸出隨機值。 #返回shape形狀矩陣:使用者數×特徵數,產生於low(-1)和high(1)之間,產生的值是均勻分佈的。 uid_embed_layer = tf.nn.embedding_lookup(uid_embed_matrix, uid, name = "uid_embed_layer") #tf.nn.embedding_lookup(tensor, id) 選取一個張量tensor裡面索引id對應的元素 #選取uid_embed_matrix的使用者id對應的某個使用者id的向量 #性別 gender_embed_matrix = tf.Variable(tf.random_uniform([gender_max, embed_dim // 2], -1, 1), name= "gender_embed_matrix") #這裡特徵數降一半 gender_embed_layer = tf.nn.embedding_lookup(gender_embed_matrix, user_gender, name = "gender_embed_layer") #選取gender_embed_matrix的使用者性別對應的某個使用者性別的向量 #年齡 age_embed_matrix = tf.Variable(tf.random_uniform([age_max, embed_dim // 2], -1, 1), name="age_embed_matrix") age_embed_layer = tf.nn.embedding_lookup(age_embed_matrix, user_age, name="age_embed_layer") #選取age_embed_matrix的使用者年齡對應的某個使用者年齡的向量 #job job_embed_matrix = tf.Variable(tf.random_uniform([job_max, embed_dim // 2], -1, 1), name = "job_embed_matrix") job_embed_layer = tf.nn.embedding_lookup(job_embed_matrix, user_job, name = "job_embed_layer") #選取job_embed_matrix的使用者job對應的某個使用者job的向量 return uid_embed_layer, gender_embed_layer, age_embed_layer, job_embed_layer #將User的嵌入矩陣一起全連線生成User的特徵 def get_user_feature_layer(uid_embed_layer, gender_embed_layer, age_embed_layer, job_embed_layer): with tf.name_scope("user_fc"): #第一層全連線 uid_fc_layer = tf.layers.dense(uid_embed_layer, embed_dim, name = "uid_fc_layer", activation=tf.nn.relu) gender_fc_layer = tf.layers.dense(gender_embed_layer, embed_dim, name = "gender_fc_layer", activation=tf.nn.relu) age_fc_layer = tf.layers.dense(age_embed_layer, embed_dim, name ="age_fc_layer", activation=tf.nn.relu) job_fc_layer = tf.layers.dense(job_embed_layer, embed_dim, name = "job_fc_layer", activation=tf.nn.relu) # tf.layers.dense(inputs,units,activation=None,use_bias=True,kernel_initializer=None,bias_initializer=tf.zeros_initializer(), # kernel_regularizer=None,bias_regularizer=None,activity_regularizer=None,kernel_constraint=None, # bias_constraint=None,trainable=True,name=None,reuse=None) #inputs:該層的輸入; units:輸出的大小(維數),整數或long; activation: 使用什麼啟用函式(神經網路的非線性層),預設為None,不使用啟用函式 #name該層的名字 #第二層全連線 user_combine_layer = tf.concat([uid_fc_layer, gender_fc_layer, age_fc_layer, job_fc_layer], 2) #(?, 1, 128) #tf.concat(values,axis,name)是連線兩個矩陣的操作,本身不會增加維度,返回的是連線後的tensor. #values應該是一個tensor的list或者tuple。 #axis則是我們想要連線的維度。對於二維來說0表示第一個括號維度,1表示第二個括號維度 #t1 = [[1, 2, 3], [4, 5, 6]] #t2 = [[7, 8, 9], [10, 11, 12]] #tf.concat([t1, t2], 0)表示為 [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]] #tf.concat([t1, t2], 1)表示為 [[1, 2, 3, 7, 8, 9], [4, 5, 6, 10, 11, 12]] # 對於三維來說0表示第一個括號維度,1表示第二個括號維度,2表示第三個括號維度 #t1 = [ [[1],[2]] , [[3],[4]] ] #t2 = [ [[5],[6]] , [[7],[8]] ] #tf.concat([t1, t2], 0)表示為[[[1],[2]] , [[3],[4]] , [[5],[6]] , [[7],[8]]] #tf.concat([t1, t2], 1)表示為[[[1],[2],[5],[6]],[[3],[4],[7],[8]]] #tf.concat([t1, t2], 2)表示為[[[1 5],[2 6]],[[3 7],[4 8]]] user_combine_layer = tf.contrib.layers.fully_connected(user_combine_layer, 200, tf.tanh) #(?, 1, 200) #tf.contrib.layers.fully_connected(F輸入, num_outputs,activation_fn)用於構建全連線層 user_combine_layer_flat = tf.reshape(user_combine_layer, [-1, 200]) #-1表示預設值,滿足其他維度要求,這裡該是幾就是幾,(?, 200) return user_combine_layer, user_combine_layer_flat #對電影型別的多個嵌入向量做加和 def get_movie_categories_layers(movie_categories): with tf.name_scope("movie_categories_layers"): movie_categories_embed_matrix = tf.Variable(tf.random_uniform([movie_categories_max, embed_dim], -1, 1), name = "movie_categories_embed_matrix") movie_categories_embed_layer = tf.nn.embedding_lookup(movie_categories_embed_matrix, movie_categories, name = "movie_categories_embed_layer")#(?,18,32) if combiner == "sum": movie_categories_embed_layer = tf.reduce_sum(movie_categories_embed_layer, axis=1, keep_dims=True)#(?,1,32) #tf.reduce_sum(input_tensor,axis=None,keep_dims=False,name=None,reduction_indices=None)要輸出看! #壓縮求和,用於降維 #函式中的input_tensor是按照axis中已經給定的維度來減少的,axis表示按第幾個維度求和 #但是keep_dims為true,則維度不會減小 #如果axis沒有條目,則縮小所有維度,並返回具有單個元素的張量 #'x' is [[1, 1, 1] # [1, 1, 1]] #求和tf.reduce_sum(x) ==> 6 #按列求和tf.reduce_sum(x, 0) ==> [2, 2, 2](即得到行結果) #按行求和tf.reduce_sum(x, 1) ==> [3, 3] (即得到列結果) #按照行的維度求和tf.reduce_sum(x, 1, keep_dims=True) ==> [[3], [3]](維度不會減少) #x現在一行為一個型別向量,應該按axis=1進行sum # elif combiner == "mean": return movie_categories_embed_layer #定義Movie ID的嵌入矩陣 def get_movie_id_embed_layer(movie_id): with tf.name_scope("movie_embedding"): movie_id_embed_matrix = tf.Variable(tf.random_uniform([movie_id_max, embed_dim], -1, 1), name = "movie_id_embed_matrix") movie_id_embed_layer = tf.nn.embedding_lookup(movie_id_embed_matrix, movie_id, name = "movie_id_embed_layer") return movie_id_embed_layer #Movie Title的文字卷積網路實現 def get_movie_cnn_layer(movie_titles): #從嵌入矩陣中得到電影名對應的各個單詞的嵌入向量 with tf.name_scope("movie_embedding"): movie_title_embed_matrix = tf.Variable(tf.random_uniform([movie_title_max, embed_dim], -1, 1), name = "movie_title_embed_matrix") movie_title_embed_layer = tf.nn.embedding_lookup(movie_title_embed_matrix, movie_titles, name = "movie_title_embed_layer") movie_title_embed_layer_expand = tf.expand_dims(movie_title_embed_layer, -1)#(?,15,32,1) #tf.expand_dims(input, axis, name=None)在axis軸,維度增加一維,數值為1,axis=-1表示最後一位 #當然,我們常用tf.reshape(input, shape=[])可達到相同效果,但有時在構建圖的過程中,placeholder沒有被feed具體的值,就會報錯 #'t' is a tensor of shape [2] # shape(expand_dims(t, 0)) ==> [1, 2] # shape(expand_dims(t, 1)) ==> [2, 1] # shape(expand_dims(t, -1)) ==> [2, 1] #對文字嵌入層使用不同尺寸的卷積核做卷積和最大池化 pool_layer_lst = [] for window_size in window_sizes:#[2,3,4,5] with tf.name_scope("movie_txt_conv_maxpool_{}".format(window_size)): #格式化字串的函式 str.format() #通過 {} 和 : 來代替以前的 % filter_weights = tf.Variable(tf.truncated_normal([window_size, embed_dim, 1, filter_num],stddev=0.1),name = "filter_weights") #初始化卷積核引數 #tf.truncated_normal(shape, mean=0.0, stddev=1.0, dtype=tf.float32, seed=None, name=None) #得到正態分佈輸出為shape,mean均值,stddev標準差 filter_bias = tf.Variable(tf.constant(0.1, shape=[filter_num]), name="filter_bias") #初始化bias #tf.constant(value,dtype=None,shape=None,name=’Const’) 建立一個常量tensor,按照給出value來賦值,可以用shape來指定其形狀 #這裡的shape表示(8,) conv_layer = tf.nn.conv2d(movie_title_embed_layer_expand, filter_weights, [1,1,1,1], padding="VALID", name="conv_layer") #tf.nn.conv2d(input, filter, strides, padding, use_cudnn_on_gpu=None, name=None) #input:指需要做卷積的輸入影象 filter:相當於CNN中的卷積核 strides:卷積時在影象每一維的步長通常為[1,X,X,1] #padding:string型別的量,只能是"SAME","VALID"其中之一,這裡不填充 #use_cudnn_on_gpu:bool型別,是否使用cudnn加速,預設為true relu_layer = tf.nn.relu(tf.nn.bias_add(conv_layer,filter_bias), name ="relu_layer") #最後迭代到windowsize=5時(?, 11, 1, 8) maxpool_layer = tf.nn.max_pool(relu_layer, [1,sentences_size - window_size + 1 ,1,1], [1,1,1,1], padding="VALID", name="maxpool_layer") #(?, 1, 1, 8) #tf.nn.max_pool(value, ksize, strides, padding, name=None) #value:需要池化的輸入 #ksize:池化視窗的大小,取一個四維向量,一般是[1, height, width, 1],因為我們不想在batch和channels上做池化,所以這兩個維度設為了1 #height=15-windowsize #strides:和卷積類似,視窗在每一個維度上滑動的步長,一般也是[1, stride,stride, 1] pool_layer_lst.append(maxpool_layer) #這裡最後得到4個tensor堆疊的列表 #[<tf.Tensor 'movie_txt_conv_maxpool_2/maxpool_layer:0' shape=(?, 1, 1, 8) dtype=float32>, #<tf.Tensor 'movie_txt_conv_maxpool_3/maxpool_layer:0' shape=(?, 1, 1, 8) dtype=float32>, #<tf.Tensor 'movie_txt_conv_maxpool_4/maxpool_layer:0' shape=(?, 1, 1, 8) dtype=float32>, #<tf.Tensor 'movie_txt_conv_maxpool_5/maxpool_layer:0' shape=(?, 1, 1, 8) dtype=float32>] #Dropout層 with tf.name_scope("pool_dropout"): pool_layer = tf.concat(pool_layer_lst, 3, name ="pool_layer") #(?, 1, 1, 32) max_num = len(window_sizes) * filter_num #32 pool_layer_flat = tf.reshape(pool_layer , [-1, 1, max_num], name = "pool_layer_flat") #(?, 1, 32) dropout_layer = tf.nn.dropout(pool_layer_flat, dropout_keep_prob, name = "dropout_layer") #(?, 1, 32) #tf.nn.dropout(x, keep_prob, noise_shape=None, seed=None, name=None) #x:輸入;keep_prob:保留比例,取值 (0,1],每一個引數都將按這個比例隨機變更 #dropout是CNN中防止過擬合的一個trick #dropout是指在深度學習網路的訓練過程中,對於神經網路單元,按照一定的概率將其暫時從網路中丟棄。 #注意是暫時,對於隨機梯度下降來說,由於是隨機丟棄,故而每一個mini-batch都在訓練不同的網路。 return pool_layer_flat, dropout_layer #將Movie的各個層一起做全連線 def get_movie_feature_layer(movie_id_embed_layer, movie_categories_embed_layer, dropout_layer): with tf.name_scope("movie_fc"): #第一層全連線 movie_id_fc_layer = tf.layers.dense(movie_id_embed_layer, embed_dim, name = "movie_id_fc_layer", activation=tf.nn.relu) movie_categories_fc_layer = tf.layers.dense(movie_categories_embed_layer, embed_dim, name = "movie_categories_fc_layer", activation=tf.nn.relu) #第二層全連線 movie_combine_layer = tf.concat([movie_id_fc_layer, movie_categories_fc_layer, dropout_layer], 2) #(?, 1, 96) movie_combine_layer = tf.contrib.layers.fully_connected(movie_combine_layer, 200, tf.tanh) #(?, 1, 200) movie_combine_layer_flat = tf.reshape(movie_combine_layer, [-1, 200])#(?,200) return movie_combine_layer, movie_combine_layer_flat #構建計算圖----------------------------------------- #變數常量等等基本量的操作設定完成,意味著最基本的東西都有了,然後接下來很重要的就是那些量和操作怎麼組成更大的集合,怎麼執行這個集合 #這些就是計算圖譜Graph和Session的作用:(參見https://blog.csdn.net/xierhacker/article/details/53860379) #一、graph #一個TensorFlow的運算,被表示為一個數據流的圖。 #一幅圖中包含一些操作(Operation)物件,這些物件是計算節點。前面說過的Tensor物件,則是表示在不同的操作(operation)間的資料節點 #一旦開始任務,就已經有一個預設的圖建立好了。可以通過呼叫tf.get_default_graph()來訪問。新增一個操作到預設的圖裡面,只需呼叫一個定義了新操作的函式 #另外一種典型用法是要使用到Graph.as_default()的上下文管理器(context manager),它能夠在這個上下文裡面覆蓋預設的圖.要在某個graph裡面定義量,要在with語句的範圍裡面定義 #二.Session(tf.Session) #執行TensorFLow操作(operations)的類,一個Seesion包含了操作物件執行的環境 # #tensorflow中的計算以圖資料流的方式表示,一個圖包含一系列表示計算單元的操作物件,以及在圖中流動的資料單元以tensor物件 tf.reset_default_graph()#用於清除預設圖形堆疊並重置全域性預設圖形 train_graph = tf.Graph()# first creat a simple graph with train_graph.as_default(): #define a simple graph #返回一個上下文管理器,使得這個Graph物件成為當前預設的graph.當你想在一個程序裡面建立多個圖的時候,就應該使用這個函式. #為了方便,一個全域性的圖物件被預設提供,要是沒有顯式建立一個新的圖的話,所有的操作(ops)都會被新增到這個預設的圖裡面來. #通過with關鍵字和這個方法,來讓這個程式碼塊內建立的從操作(ops)新增到這個新的圖裡面. #with Object() as obj:的時候,自動呼叫obj物件的__enter__()方法,而當出去with block的時候,又會呼叫obj物件的__exit__方法。 #正是利用 __enter__()和__exit__(),才實現類似上下文管理器的作用。 #參見https://blog.csdn.net/u012436149/article/details/73555017 #獲取輸入佔位符 uid, user_gender, user_age, user_job, movie_id, movie_categories, movie_titles, targets, lr, dropout_keep_prob = get_inputs() #獲取User的4個嵌入向量 uid_embed_layer, gender_embed_layer, age_embed_layer, job_embed_layer = get_user_embedding(uid, user_gender, user_age, user_job) #得到使用者特徵 user_combine_layer, user_combine_layer_flat = get_user_feature_layer(uid_embed_layer, gender_embed_layer, age_embed_layer, job_embed_layer) #獲取電影ID的嵌入向量 movie_id_embed_layer = get_movie_id_embed_layer(movie_id) #獲取電影型別的嵌入向量 movie_categories_embed_layer = get_movie_categories_layers(movie_categories) #獲取電影名的特徵向量 pool_layer_flat, dropout_layer = get_movie_cnn_layer(movie_titles) #得到電影特徵 movie_combine_layer, movie_combine_layer_flat = get_movie_feature_layer(movie_id_embed_layer, movie_categories_embed_layer, dropout_layer) #計算出評分,要注意兩個不同的方案,inference的名字(name值)是不一樣的,後面做推薦時要根據name取得tensor with tf.name_scope("inference"): #將使用者特徵和電影特徵作為輸入,經過全連線,輸出一個值的方案 # inference_layer = tf.concat([user_combine_layer_flat, movie_combine_layer_flat], 1) #(?, 200) # inference = tf.layers.dense(inference_layer, 1, # kernel_initializer=tf.truncated_normal_initializer(stddev=0.01), # kernel_regularizer=tf.nn.l2_loss, name="inference") #簡單的將使用者特徵和電影特徵做矩陣乘法得到一個預測評分 # inference = tf.matmul(user_combine_layer_flat, tf.transpose(movie_combine_layer_flat)) 錯誤應該是下面一個使用者與一個電影的評分 inference = tf.reduce_sum(user_combine_layer_flat * movie_combine_layer_flat, axis=1)#按axis=1求和降維,得(?,),*表示對應元素相乘 inference = tf.expand_dims(inference, axis=1) #(batch,1)為了下面和target統一格式計算loss with tf.name_scope("loss"): #將梯度在target network和learner間傳遞的功能在distributed tensorflow中預設已經實現好了 #Between-graph的方式中,每個thread會拷貝一份Graph,計算後回傳回主Graph。需要解決的主要是梯度累積的問題。 # MSE損失,將計算值迴歸到評分 cost = tf.losses.mean_squared_error(targets, inference ) loss = tf.reduce_mean(cost) #tf.reduce_mean 函式用於計算張量tensor沿著指定的數軸(tensor的某一維度)上的的平均值,主要用作降維或者計算tensor(影象)的平均值。 # 優化損失 # train_op = tf.train.AdamOptimizer(lr).minimize(loss) #cost global_step = tf.Variable(0, name="global_step", trainable=False) optimizer = tf.train.AdamOptimizer(lr)#進行反向傳播的訓練方法 gradients = optimizer.compute_gradients(loss) #cost #minimize() = compute_gradients() + apply_gradients()拆分成計算梯度和應用梯度兩個步驟 #tf.train.Optimizer.compute_gradients(loss, var_list=None, gate_gradients=1) 計算“var_list”中變數的“loss”梯度 #返回一個元素為(loss對引數梯度,這個引數引數)對的列表[ (), () ],其中每個tuple對中梯度是該引數的梯度值 #loss:包含要最小化的值的Tensor train_op = optimizer.apply_gradients(gradients, global_step=global_step) #tf.train.Optimizer.apply_gradients(grads_and_vars, global_step=None, name=None)進行更新網路,即應用梯度 #minimize() 的第二部分,將上一步得到的字典,進行更新引數 #grads_and_vars: 上一步得到的[(gradient, variable)] #取得batch------------------------------------------ def get_batches(Xs, ys, batch_size): for start in range(0, len(Xs), batch_size): end = min(start + batch_size, len(Xs)) yield Xs[start:end], ys[start:end] #yield 是一個類似 return 的關鍵字,迭代一次遇到yield時就返回yield後面(右邊)的值。 #重點是:下一次迭代時,從上一次迭代遇到的yield後面的程式碼(下一行)開始執行。 #參見https://www.jianshu.com/p/d09778f4e055 #訓練網路------------------------------------------- %matplotlib inline #ipython notebook中有一個相當方便的語句: %matplotlib inline,可以實現執行cell即出現結果影象。 #但是如果想寫在Python程式內,顯示影象,需要加上一句: plt.show()。 #當然確保匯入了對應的庫: import matplotlib.pyplot as plt #魔法函式(Magic Functions)分兩種:一種是面向行的,另一種是面向單元型的。 #行magic函式:用字首“%”標註的,很像使用命令列時的形式,“%”後面就是magic函式的引數了,但引數是沒有被寫在括號或者引號中來傳值的 #單元型magic函式是由兩個“%%”做字首的,它的引數不僅是當前“%%”行後面的內容,也包括了在當前行以下的行 %config InlineBackend.figure_format = 'retina' #為了使畫出來的圖支援 retina #在解析度較高的螢幕(例如 Retina 顯示屏)上,notebook 中的預設影象可能會顯得模糊。 #可以在 %matplotlib inline 之後使用 %config InlineBackend.figure_format = 'retina' 來呈現解析度較高的影象。 losses = {'train':[], 'test':[]} with tf.Session(graph=train_graph) as sess: #蒐集資料給tensorBoard用 #跟蹤梯度值和稀疏度 grad_summaries = [] for g, v in gradients: #v對應的gradients if g is not None: grad_hist_summary = tf.summary.histogram("{}/grad/hist".format(v.name.replace(':', '_')), g) #tf.summary.histogram('summary_name', tensor)用來顯示直方圖資訊 #將【計算圖】中的【資料的分佈/資料直方圖】寫入TensorFlow中的【日誌檔案】,以便為將來tensorboard的視覺化做準備 #一般用來顯示訓練過程中變數的分佈情況 sparsity_summary = tf.summary.scalar("{}/grad/sparsity".format(v.name.replace(':', '_')), tf.nn.zero_fraction(g)) #tf.summary.scalar(name, tensor, collections=None) 用來顯示標量資訊,一般在畫loss,accuary時會用到這個函式 #將【計算圖】中的【標量資料】寫入TensorFlow中的【日誌檔案】,以便為將來tensorboard的視覺化做準備 #tf.nn.zero_fraction統計某個值的0的比例,這個tf.nn.zero_fraction計算出來的值越大,0的比例越高,稀疏度 grad_summaries.append(grad_hist_summary) grad_summaries.append(sparsity_summary) grad_summaries_merged = tf.summary.merge(grad_summaries) #tf.summary.merge(inputs, collections=None, name=None)將上面幾種型別的彙總再進行一次合併,具體合併哪些由inputs指定 # merge_all將之前定義的所有summary整合在一起 #[2]和TensorFlow中的其他操作類似,tf.summary.scalar、tf.summary.histogram、tf.summary.image函式也是一個op #在定義的時候,也不會立即執行,需要通過sess.run來明確呼叫這些函式。因為,在一個程式中定義的寫日誌操作比較多 #如果一一呼叫,將會十分麻煩,所以Tensorflow提供了tf.summary.merge_all()函式將所有的summary整理在一起 #在TensorFlow程式執行的時候,只需要執行這一個操作就可以將程式碼中定義的所有【寫日誌操作】執行一次,從而將所有的日誌寫入【日誌檔案】。 #模型和summaries的輸出目錄 timestamp = str(int(time.time())) out_dir = os.path.abspath(os.path.join(os.path.curdir, "runs", timestamp)) #os.path.curdir:當前目錄 #os.path.join(): 常用來連結路徑 #os.path.abspath 絕對路徑 #Python中有join()和os.path.join()兩個函式 #join(): 連線字串陣列。將字串、元組、列表中的元素以指定的字元(分隔符)連線生成一個新的字串 #os.path.join(): 將多個路徑組合後返回:os.path.join(path1[,path2[,……]]) #第一個以”/”開頭的引數開始拼接,之前的引數全部丟棄 #以上一種情況為先。在上一種情況確保情況下,若出現”./”開頭的引數,會從”./”開頭的引數的上一個引數開始拼接 # #os.path.join('aaaa','/bbbb','ccccc.txt')--> /bbbb\ccccc.txt只有一個以”/”開頭的,引數從它開始往後拼接,之前的引數全部丟棄 #os.path.join('/aaaa','/bbbb','/ccccc.txt')-->/ccccc.txt有多個以”/”開頭的引數,從最後”/”開頭的的開始往後拼接,之前的引數全部丟棄 #os.path.join('aaaa','./bbb','ccccc.txt')-->aaaa\./bbb\ccccc.txt若出現”./”開頭的引數,會從”./”開頭的引數的上一個引數開始拼接,即只包含上一個引數加往後的 print("Writing to {}\n".format(out_dir)) #關於損失和準確性的summaries,顯示loss標量資訊 loss_summary = tf.summary.scalar("loss", loss) #訓練 Summaries train_summary_op = tf.summary.merge([loss_summary, grad_summaries_merged]) train_summary_dir = os.path.join(out_dir, "summaries", "train") train_summary_writer = tf.summary.FileWriter(train_summary_dir, sess.graph) #FileWriter類提供了一種用於在給定目錄下建立事件檔案的機制,並且將summary資料寫入硬碟 #sess.graph這個引數表示將前面定義的框架資訊收集起來,放在train_summary_dir目錄下 # Inference summaries inference_summary_op = tf.summary.merge([loss_summary]) inference_summary_dir = os.path.join(out_dir, "summaries", "inference") inference_summary_writer = tf.summary.FileWriter(inference_summary_dir, sess.graph) #tf.summary.FileWritter(path,sess.graph)指定一個檔案用來儲存圖 #可以呼叫其add_summary()方法將訓練過程資料儲存在filewriter指定的檔案中 sess.run(tf.global_variables_initializer()) #引數的初始化 saver = tf.train.Saver() #用於後面儲存資料,建立一個saver物件 for epoch_i in range(num_epochs): #將資料集分成訓練集和測試集,隨機種子不固定 train_X,test_X, train_y, test_y = train_test_split(features, targets_values, test_size = 0.2, random_state = 0) train_batches = get_batches(train_X, train_y, batch_size)#在分好的訓練集中再選batch個 test_batches = get_batches(test_X, test_y, batch_size) #訓練的迭代,儲存訓練損失 for batch_i in range(len(train_X) // batch_size): #//取結果的最小整數 x, y = next(train_batches) #next() 返回迭代器的下一個專案,next(get_batches(train_X, train_y, batch_size)) #在這個for迴圈每次都進行上一個batch的後面一個batch categories = np.zeros([batch_size, 18]) for i in range(batch_size): categories[i] = x.take(6,1)[i] #x取縱著的下標為6的全部資料 titles = np.zeros([batch_size, sentences_size]) for i in range(batch_size): titles[i] = x.take(5,1)[i] feed = { uid: np.reshape(x.take(0,1), [batch_size, 1]), user_gender: np.reshape(x.take(2,1), [batch_size, 1]), user_age: np.reshape(x.take(3,1), [batch_size, 1]), user_job: np.reshape(x.take(4,1), [batch_size, 1]), movie_id: np.reshape(x.take(1,1), [batch_size, 1]), movie_categories: categories, #x.take(6,1) movie_titles: titles, #x.take(5,1) targets: np.reshape(y, [batch_size, 1]), dropout_keep_prob: dropout_keep, #dropout_keep lr: learning_rate} step, train_loss, summaries, _ = sess.run([global_step, loss, train_summary_op, train_op], feed) #cost losses['train'].append(train_loss) train_summary_writer.add_summary(summaries, step) # # Show every <show_every_n_batches> batches if (epoch_i * (len(train_X) // batch_size) + batch_i) % show_every_n_batches == 0: time_str = datetime.datetime.now().isoformat() print('{}: Epoch {:>3} Batch {:>4}/{} train_loss = {:.3f}'.format( time_str, epoch_i, batch_i, (len(train_X) // batch_size), train_loss)) #使用測試資料的迭代 for batch_i in range(len(test_X) // batch_size): x, y = next(test_batches) categories = np.zeros([batch_size, 18]) for i in range(batch_size): categories[i] = x.take(6,1)[i] titles = np.zeros([batch_size, sentences_size]) for i in range(batch_size): titles[i] = x.take(5,1)[i] feed = { uid: np.reshape(x.take(0,1), [batch_size, 1]), user_gender: np.reshape(x.take(2,1), [batch_size, 1]), user_age: np.reshape(x.take(3,1), [batch_size, 1]), user_job: np.reshape(x.take(4,1), [batch_size, 1]), movie_id: np.reshape(x.take(1,1), [batch_size, 1]), movie_categories: categories, #x.take(6,1) movie_titles: titles, #x.take(5,1) targets: np.reshape(y, [batch_size, 1]), dropout_keep_prob: 1, lr: learning_rate} step, test_loss, summaries = sess.run([global_step, loss, inference_summary_op], feed) #cost #儲存測試損失 losses['test'].append(test_loss) inference_summary_writer.add_summary(summaries, step) #呼叫add_summary()方法將訓練過程資料儲存在filewriter指定的檔案中 time_str = datetime.datetime.now().isoformat() #datetime.datetime:表示日期(year, month, day)時間(hour, minute, second, microsecond) #date.isoformat():返回格式如'YYYY-MM-DD’的字串 if (epoch_i * (len(test_X) // batch_size) + batch_i) % show_every_n_batches == 0: print('{}: Epoch {:>3} Batch {:>4}/{} test_loss = {:.3f}'.format( time_str, epoch_i, batch_i, (len(test_X) // batch_size), test_loss)) # Save Model儲存模型 saver.save(sess, save_dir) #sess是一個session物件,這裡save_dir是給模型起的名字 #可以加引數 global_step=epoch_i,表示進行epoch_i次儲存一次模型 print('Model Trained and Saved') save_params((save_dir)) #儲存引數 儲存save_dir 在生成預測時使用 #顯示訓練Loss plt.plot(losses['train'], label='Training loss')#plt.plot(x,y) plt.legend() #用於顯示圖例 _ = plt.ylim() #設定y軸刻度的取值範圍,不指定則自適應橫縱座標軸 #顯示測試loss plt.plot(losses['test'], label='Test loss') plt.legend() _ = plt.ylim()
模型結構如圖(我自己寫的)