tensorflow1.xでモデルの保存と読み込み

tensorflow1.xでモデルの保存と読み込み

こんにちは、最近リアルが忙しかった怠惰人間です。かなり久しぶりの更新をしたいと思います。

今回は、tensorflowを使用してDeep Learningなどを学習した際に、そのデータを保存する・読み込む方法についてやっていこうと思います。

<2021/8/4 追記>

このページが人気なので,本ページの実行環境をGoogle Colabに提供します.ここからアクセスして実行してみてください.

セーブの種類

tensorflowのセーブは、セッション全体を保存することで行われます。また、tensorflowでは、データの保存の際に、学習途中で保存するものとそうでないものの2パターンが存在します。この2つでは、微妙に操作がこのなってくるので注意が必要です。では、早速見ていきましょう。

学習の結果のみを保存する場合

もっともシンプルな方法になります。やり方はtf.train.Saver().save()メソッドと、tf.train.Saver().restore()メソッドを使うだけです。では、さっそくコードを見てみましょう。また、このソースコードは、5行目にある「flag」変数を「True」にするとモデルの学習を、「False」にするとモデルの読み込みをするプログラムとなっています。もちろん、モデルの学習を行わずに「False」に設定した場合には、エラーが発生します。

#File Name = "save.py"
import tensorflow as tf
import random

#True=セーブ,False=読み込み
flag=False

#Create model
x=tf.placeholder(tf.float32,[1,2])

#Neural Network
w=tf.Variable(tf.truncated_normal([2,1],stddev=0.1))
b=tf.Variable(tf.truncated_normal([1,1],stddev=0.1))
fc=tf.add(tf.matmul(x,w),b)

#loss function
t=tf.placeholder(tf.float32,[1,1])
loss=0.5*tf.reduce_sum(tf.square(fc-t))

#Optimizer
optimizer=tf.train.GradientDescentOptimizer(0.01).minimize(loss)

#セッションの実行
with tf.Session() as s:
    if flag:
        #変数初期化
        s.run(tf.initialize_all_variables())

        #答えの生成
        weight=[[10],[3]]
        bias=[[5]]
            

        #学習
        for _ in range(0,1000):
            #学習データの生成
            input_data=[
                [
                    random.uniform(-5.0,5.0),
                    random.uniform(-5.0,5.0)
                ]
            ]
            #[-1,1]のノイズを加えた結果を生成
            teacher_data=[
                [
                    weight[0][0]*input_data[0][0]+
                    weight[1][0]*input_data[0][1]+
                    bias[0][0]+
                    random.uniform(-1.0,1.0)
                ]
            ]

            s.run(optimizer,feed_dict={x:input_data,t:teacher_data})
        
        #ハイパーパラメータの確認
        print("Save Weight and Bias")
        print("Weight =",s.run(w))
        print("Bias =",s.run(b))

        #モデルの保存
        tf.train.Saver().save(s,"./model_data")

    else:
        #モデルの読みこみ
        tf.train.Saver().restore(s,"./model_data")

        #読み込んだモデルのハイパーパラメータを確認
        print("Check Weight and Bias")
        print("Weight =",s.run(w))
        print("Bias =",s.run(b))
<実行結果 flag=Trueの場合>
 Save Weight and Bias
 Weight = [[9.989466 ]
  [2.9999075]]
 Bias = [[5.034903]]

<実行結果 flag=Falseの場合>
 Check Weight and Bias
 Weight = [[9.989466 ]
  [2.9999075]]
 Bias = [[5.034903]]

結果を見ると、重みとバイアスの数値が同じであることがわかります。正常に保存と読み込みができていますね。ソースコードも保存に1行、読み込みに1行ととてもわかりやすいです。

また、学習結果のディレクトリの中身は、今回のプログラム名を「save.py」とすると

  • save.py
  • checkpoint
  • model_data.data-00000-of-00001
  • model_data.index
  • model_data.meta

となっているはずです。それぞれのファイルの意味を説明します。

「checkpoint」ファイルは、モデルの学習で使用されるファイルであり、ファイル内には、モデルのデータを保存した最新のファイルがどれであるのかを示すデータが格納されています。保存したデータから再度学習を始める場合に必要です。

「model_data.data-00000-of-00001」ファイルは、変数名をテンソル値としてマッピングした独自のフォーマットです。

「model_data.index」ファイルはバイナリファイルです。複数のstepでデータを保存した際に、同名の「.data-00000-of-00001」ファイルが、どのstepのデータであるのかを一意に定めます。

「model_data.meta」ファイルは、バイナリファイルです。ファイルには、モデルのネットワーク構造が格納されており、読み込む事でソースコード内にネットワークの定義が存在しなくても実行可能になります。 

ちなみに、最低限のモデルの読み込みに必要なファイルは「model_data.data-00000-of-00001」と「model_data.index」のみであり、他は必要ありません。

重要なことをまとめると、こうなります。

  • モデルの保存:
    tf.train.Saver().save(セッション格納変数,セッション保存ファイル名)
  • モデルの読み込み:
    tf.train.Saver().restore(セッション格納変数,セッション保存ファイル名)

学習途中の結果を保存する場合

モデルを複数回保存する

前章では、単純なモデルの保存と読み込みを行いました。しかし、モデルの学習時には、停電やエラーなどといった様々な問題により、学習が中断されることがあります。1時間程度の学習で済むような小規模なモデルの学習であればやり直せば良いのですが、1日や1週間といった長い期間をかけて行う学習の場合には、学習の中断は致命的な問題になります。そのため、tensorflowではモデルの学習中に保存を行うことが可能になっています。 複数回の保存は、tf.train.Saver().save()メソッドの引数に「global_step」を設定して、複数回実行するだけです。さっそくプログラムをみていきましょう。また、このプログラムは、上記のものとほとんどの点で同じです。

#File Name = "save2.py"
import tensorflow as tf
import random

#True=セーブ,False=読み込み
flag=False

#Create model
x=tf.placeholder(tf.float32,[1,2])

#Neural Network
w=tf.Variable(tf.truncated_normal([2,1],stddev=0.1))
b=tf.Variable(tf.truncated_normal([1,1],stddev=0.1))
fc=tf.add(tf.matmul(x,w),b)

#loss function
t=tf.placeholder(tf.float32,[1,1])
loss=0.5*tf.reduce_sum(tf.square(fc-t))

#Optimizer
optimizer=tf.train.GradientDescentOptimizer(0.01).minimize(loss)

#セッションの実行
with tf.Session() as s:
    sr=tf.train.Saver()#追加!

    if flag:
        #変数初期化
        s.run(tf.initialize_all_variables())

        #答えの生成
        weight=[[10],[3]]
        bias=[[5]]
            

        #学習
        i=0
        for i in range(0,1000):
            #学習データの生成
            input_data=[
                [
                    random.uniform(-5.0,5.0),
                    random.uniform(-5.0,5.0)
                ]
            ]
            #[-1,1]のノイズを加えた結果を生成
            teacher_data=[
                [
                    weight[0][0]*input_data[0][0]+
                    weight[1][0]*input_data[0][1]+
                    bias[0][0]+
                    random.uniform(-1.0,1.0)
                ]
            ]

            #ここから追加!
            if i==500:
                print("Save Weight and Bias at 500")
                print("Weight =",s.run(w))
                print("Bias =",s.run(b))
                sr.save(s,"./model_data",global_step=i)
            #ここまで追加!

            s.run(optimizer,feed_dict={x:input_data,t:teacher_data})
        
        #ハイパーパラメータの確認
        print()
        print("Save Weight and Bias at 1000")
        print("Weight =",s.run(w))
        print("Bias =",s.run(b))

        #モデルの保存
        sr.save(s,"./model_data",global_step=i+1)#変更!

    else:
        #変更開始!
        #モデルの読みこみ
        sr.restore(s,"./model_data-500")

        #読み込んだモデルのハイパーパラメータを確認
        print("Check Weight and Bias at 500")
        print("Weight =",s.run(w))
        print("Bias =",s.run(b))

        #モデルの読みこみ
        sr.restore(s,"./model_data-1000")

        #読み込んだモデルのハイパーパラメータを確認
        print()
        print("Check Weight and Bias at 1000")
        print("Weight =",s.run(w))
        print("Bias =",s.run(b))
        #ここまで変更!
<実行結果 flag=True>
 Save Weight and Bias at 500
 Weight = [[10.031795 ]
  [ 2.9998863]]
 Bias = [[5.002645]]

 Save Weight and Bias at 1000
 Weight = [[9.96026  ]
  [3.0712578]]
 Bias = [[5.024471]]

<実行結果 flag=False>
 Check Weight and Bias at 500
 Weight = [[10.031795 ]
  [ 2.9998863]]
 Bias = [[5.002645]]

 Check Weight and Bias at 1000
 Weight = [[9.96026  ]
  [3.0712578]]
 Bias = [[5.024471]]

異なるstep数での重みとバイアスを読み込めていることがわかります。ただ、注意するべきことは、ファイルが保存したstep数ごとに分かれているということです。このプログラムを「save2.py」として実行した際に、プログラムが格納されているディレクトリは前のプログラムを実行した時の結果と異なり、

  • save2.py
  • checkpoint
  • model_data-500.data-00000-of-00001
  • model_data-500.index
  • model_data-500.meta
  • model_data-1000.data-00000-of-00001
  • model_data-1000.index
  • model_data-1000.meta

となっています。見ればわかるのですが、step数ごとにcheckpoint以外のファイルが作成されていることがわかります。ここで注目して欲しいのは、ファイル拡張子ではなく、ファイル名の方にstep数が追加されていることです。つまり、このデータの読み込みの際には、保存の際に指定した「./model_data」では、正しく読み込めないということです。読み込みの際には、読み込みたいstep数を決めて「./model_data-500」のようにする必要性があります。

最も新しく保存されたファイルを探索する

最も新しく保存したファイル(最も大きいstep数のファイルではない)の名前はtf.train.latest_checkpoint(探索ディレクトリのパス)で検索することができます。実際に、上記のプログラム(save2.pyのこと)実行後のディレクトリ内でプログラムを実行してみましょう。

#File Name = "search.py"
import tensorflow as tf
path=tf.train.latest_checkpoint("./")
print(path)
<実行結果>
./model_data-1000

実際に最後に保存されたファイルのパスが取得できます。

保存上限の解除

tensorflowで、複数回の保存を行った場合、5回よりも前に保存されたデータは削除されてしまいます。ここでは、その上限の変更、または解除の方法をみていきます。

まずは、本当に5個以上保存されないのかの確認です。上記のプログラム(save2.py)の保存部分を以下のように変更します。この変更は、1000stepの学習において、10stepごとにモデルの情報を保存するというものです。

            if i%10==0:
                print("Save Weight and Bias at",i)
                print("Weight =",s.run(w))
                print("Bias =",s.run(b))
                sr.save(s,"./model_data",global_step=i)

改造したプログラムを実行した後にディレクトリを確認してみると、実際に最後の5回分しか保存されていないことがわかります。

この問題を解決は、tf.train.Saver()メソッドの引数に「max_to_keep」の引数を追加することで解決できます。この引数はモデルの情報を保存する最大数を定義するもので、デフォルトでは5です。なお、この引数にNoneを与えることで、無数にデータを保存することができます。

では、実際にtf.train.Saver()メソッドに引数を追加して実行してみましょう。

#File Name = "save3.py"
import tensorflow as tf
import random

#Create model
x=tf.placeholder(tf.float32,[1,2])

#Neural Network
w=tf.Variable(tf.truncated_normal([2,1],stddev=0.1))
b=tf.Variable(tf.truncated_normal([1,1],stddev=0.1))
fc=tf.add(tf.matmul(x,w),b)

#loss function
t=tf.placeholder(tf.float32,[1,1])
loss=0.5*tf.reduce_sum(tf.square(fc-t))

#Optimizer
optimizer=tf.train.GradientDescentOptimizer(0.01).minimize(loss)

#セッションの実行
with tf.Session() as s:
    sr=tf.train.Saver(max_to_keep=None)#追加!

    #変数初期化
    s.run(tf.initialize_all_variables())

    #答えの生成
    weight=[[10],[3]]
    bias=[[5]]
        

    #学習(Epoch=1000)
    i=0
    for i in range(0,1000):
        #学習データの生成
        input_data=[
            [
                random.uniform(-5.0,5.0),
                random.uniform(-5.0,5.0)
            ]
        ]
        #[-1,1]のノイズを加えた結果を生成
        teacher_data=[
            [
                weight[0][0]*input_data[0][0]+
                weight[1][0]*input_data[0][1]+
                bias[0][0]+
                random.uniform(-1.0,1.0)
            ]
        ]

        if i%10==0:
            print("Save Weight and Bias at",i)
            print("Weight =",s.run(w))
            print("Bias =",s.run(b))
            sr.save(s,"./model_data",global_step=i)

        s.run(optimizer,feed_dict={x:input_data,t:teacher_data})
    
    #ハイパーパラメータの確認
    print()
    print("Save Weight and Bias at 1000")
    print("Weight =",s.run(w))
    print("Bias =",s.run(b))

    #モデルの保存
    sr.save(s,"./model_data",global_step=i+1)

ディレクトリを確認すると、実際に100個のデータが保存されたことがわかると思います。

まとめると、

  • 複数回モデルを保存:tf.train.Saver().save()にglobal_step引数を追加する
  • 複数回モデルを保存したときには、ファイル名に「-step数」をつける
  • 最新のモデル保存ファイル:tf.train.latest_checkpoint(ディレクトリのパス)
  • モデルの保存上限数設定:tf.train.Saver()にmax_to_keepの引数を設定する
  • max_to_keep=Noneは保存上限数なし

となります。

ネットワークの読み込み

最後に、ネットワークの読み込みです。tensorflowはモデルを読み込むときに、モデルを保存した際に存在した変数と同じものが必要になります。これが存在しない場合、読み込みはエラーとなります。では、実際にソースコードでエラーが発生するかを確認してみましょう。以下のソースコードは、引数によって処理が変わります。引数を0から5の範囲内で実行して、結果の違いを確認してみてください。

#File Name = "LoadNetTest.py"
import tensorflow as tf
import sys

def CreateSaveData():
    #ランダムに変数を1つ生成
    val=tf.Variable(tf.truncated_normal((1,),stddev=0.1))

    with tf.Session() as s:
        #変数の初期化
        s.run(tf.initialize_all_variables())
        #変数の表示
        print("Save Value =",s.run(val))
        #変数の保存
        tf.train.Saver().save(s,"./savingdata")

def LoadWithNothingValue():
    #変数が存在しない場合
    with tf.Session() as l0:
        #データの読みこみ(失敗時のためにtry-catchを使用)
        try:
            tf.train.Saver().restore(l0,"./savingdata")
            print("Load Value =",l0.run(val))
        except:
            print("読み込み失敗")
        else:
            print("読み込み成功")       

def LoadWithMoreValue():
    #保存時よりも多くの変数が存在
    val=tf.Variable(tf.truncated_normal((1,),stddev=0.1))
    test=tf.Variable(tf.truncated_normal((1,),stddev=0.1))

    with tf.Session() as l1:
        #データの読みこみ(失敗時のためにtry-catchを使用)
        try:
            tf.train.Saver().restore(l1,"./savingdata")
            print("Load Value =",l1.run(val))
        except:
            print("読み込み失敗")
        else:
            print("読み込み成功")

def LoadWithProcessing():
    #保存時の計算グラフに追加して処理用の式が存在
    val=tf.Variable(tf.truncated_normal((1,),stddev=0.1))
    test=tf.add(val,1.0)

    with tf.Session() as l2:
        #データの読みこみ(失敗時のたsめにtry-catchを使用)
        try:
            tf.train.Saver().restore(l2,"./savingdata")
            print("Load Value =",l2.run(val))
        except:
            print("読み込み失敗")
        else:
            print("読み込み成功")

def LoadWithPlaceholder():
    #保存時の計算グラフに追加でプレースホルダー存在
    val=tf.Variable(tf.truncated_normal((1,),stddev=0.1))
    test=tf.placeholder(tf.float32,[1,])

    with tf.Session() as l3:
        #データの読みこみ(失敗時のたsめにtry-catchを使用)
        try:
            tf.train.Saver().restore(l3,"./savingdata")
            print("Load Value =",l3.run(val))
        except:
            print("読み込み失敗")
        else:
            print("読み込み成功")

def LoadWithWrongValueName():
    #変数の名前が異なる
    mistaken=tf.Variable(tf.truncated_normal((1,),stddev=0.1))

    with tf.Session() as l4:
        #データの読みこみ(失敗時のたsめにtry-catchを使用)
        try:
            tf.train.Saver().restore(l4,"./savingdata")
            print("Load Value =",l4.run(val))
        except:
            print("読み込み失敗")
        else:
            print("読み込み成功")

#引数を読み取る
args=sys.argv

#引数によって、処理を変える
#(一度にモデルを定義することはできないため)

if len(args)<2:
    processnum=0
else:
    processnum=int(args[1])

if processnum==0:
    print("データを保存")
    CreateSaveData()
elif processnum==1:
    print("変数を定義しない場合")
    LoadWithNothingValue()
elif processnum==2:
    print("変数を多く定義した場合")
    LoadWithMoreValue()
elif processnum==3:
    print("処理用の式を追加した場合")
    LoadWithProcessing()
elif processnum==4:
    print("プレースホルダーを追加した場合")
    LoadWithPlaceholder()
elif processnum==5:
    print("変数名を変えて定義した場合")
    LoadWithWrongValueName()
<実行結果 引数=0>
 データを保存
 Save Value = [0.15969686]

<実行結果 引数=1>
 変数を定義しない場合
 読み込み失敗

<実行結果 引数=2>
 変数を多く定義した場合
 読み込み失敗

<実行結果 引数=3>
 処理用の式を追加した場合
 Load Value = [0.15969686]
 読み込み成功

<実行結果 引数=4>
 プレースホルダーを追加した場合
 Load Value = [0.15969686]
 読み込み成功

<実行結果 引数=5>
 変数名を変えて定義した場合
 読み込み失敗

この結果から、今までのやり方でモデルを読み込む条件としては、モデルに同じ数の変数が存在し、かつその変数の名前が同じであることがわかります。

しかし、毎回同じネットワークを定義するのは面倒です。さらに、別のネットワークを同時に定義したい際にもこの制約は邪魔です。そのため、ネットワークの定義なしにモデルを読み込む方法がtensorflowには用意されています。それは、tf.train.import_meta_graph()メソッドです。このメソッドは、「.meta」ファイルを読み込むことで、ネットワーク構造ごと復元を行うことができます。これは、tf.train.Saver()メソッドの代わりに使用されます。また、ネットワークを復元する際には使用したい変数、式、プレースホルダーにはname引数を使わなくてはいけません。さらに、読み込んだ後に変数や式を実行する場合には、文字列型で指定します。その際、指定方法は「”name引数に指定した値”:0」となります。では、実際にソースコードで確認してみましょう。

#File Name = "AutoNetworkloader.py"
import tensorflow as tf
import random

#True=セーブ,False=読み込み
flag=False

def Save():
    #モデルの学習と保存

    #Create model
    x=tf.placeholder(tf.float32,[1,2],name="x")

    #Neural Network
    w=tf.Variable(tf.truncated_normal([2,1],stddev=0.1),name="w")
    b=tf.Variable(tf.truncated_normal([1,1],stddev=0.1),name="b")
    fc=tf.add(tf.matmul(x,w),b,name="fc")

    #loss function
    t=tf.placeholder(tf.float32,[1,1])
    loss=0.5*tf.reduce_sum(tf.square(fc-t))

    #Optimizer
    optimizer=tf.train.GradientDescentOptimizer(0.01).minimize(loss)

    #セッションの実行
    with tf.Session() as s:
        #変数初期化
        s.run(tf.initialize_all_variables())

        #答えの生成
        weight=[[10],[3]]
        bias=[[5]]

        #学習(Epoch=1000)
        for _ in range(0,1000):
            #学習データの生成
            input_data=[
                [
                    random.uniform(-5.0,5.0),
                    random.uniform(-5.0,5.0)
                ]
            ]
            #[-1,1]のノイズを加えた結果を生成
            teacher_data=[
                [
                    weight[0][0]*input_data[0][0]+
                    weight[1][0]*input_data[0][1]+
                    bias[0][0]+
                    random.uniform(-1.0,1.0)
                ]
            ]

            s.run(optimizer,feed_dict={x:input_data,t:teacher_data})
        
        #ハイパーパラメータの確認
        print("Save Weight and Bias")
        print("Weight =",s.run(w))
        print("Bias =",s.run(b))

        #モデルの保存
        tf.train.Saver().save(s,"./model_data")

def Load():
    #モデルの読み込みと実行
    add=tf.Variable(10)

    #セッションの実行
    with tf.Session() as s:
        s.run(tf.initialize_all_variables())

        #モデルの読みこみ
        loader=tf.train.import_meta_graph("./model_data.meta")
        loader.restore(s,"./model_data")

        #読み込んだモデルのハイパーパラメータを確認
        #(インスタンスがないので、stringで指定する)
        print("Check Weight and Bias")
        print("Weight =",s.run("w:0"))
        print("Bias =",s.run("b:0"))
        
        #インスタンスとしてアクセスできるようにする
        fc_instance=tf.get_default_graph().get_tensor_by_name("fc:0")

        print("Test Run",s.run(fc_instance,feed_dict={"x:0":[[1,1]]}))

        print(s.run(add))

if flag:
    Save()
else:
    Load()
<実行結果 flag=True>
 Save Weight and Bias
 Weight = [[9.933189 ]
  [2.9303164]]
 Bias = [[5.0090404]]

<実行結果 flag=False>
 Check Weight and Bias
 Weight = [[9.933189 ]
  [2.9303164]]
 Bias = [[5.0090404]]
 Test Run [[17.872547]]
 10

プログラムを見てみると、実際にモデルを定義しなくても実行できており、なおかつ、新しい変数も定義できていることがわかります。また、tf.get_default_graph().get_tensor_by_name()メソッドを使用することで、文字列ではなく、変数として式を呼び出すこともできています。

まとめると、

  • モデルを読み込む際には、変数の名前、数が一致している必要がある。
  • ネットワークを保存する際には使用したい式、変数、プレースホルダーにname引数を追加
  • ネットワークを読み込む:tf.train.import_meta_graph(.metaファイル)
    ※tf.train.Saver()の代わりに使うこと。
  • ネットワークを読み込んだ際に、実行するには「”name引数に指定した値”:0」で読み込む
  • tf.get_default_graph().get_tensor_by_name(「”name引数に指定した値”:0」)でpythonの変数として取得が可能。

です。

今日の内容は以上!長かったですね。お疲れ様です。

まとめ

  • モデルの保存:
    tf.train.Saver().save(セッション格納変数,セッション保存ファイル名)
  • モデルの読み込み:
    tf.train.Saver().restore(セッション格納変数,セッション保存ファイル名)
  • 複数回モデルを保存:tf.train.Saver().save()にglobal_step引数を追加する
  • 複数回モデルを保存したときには、ファイル名に「-step数」をつける
  • 最新のモデル保存ファイル:tf.train.latest_checkpoint(ディレクトリのパス)
  • モデルの保存上限数設定:tf.train.Saver()にmax_to_keepの引数を設定する
  • max_to_keep=Noneは保存上限数なし
  • モデルを読み込む際には、変数の名前、数が一致している必要がある。
  • ネットワークを保存する際には使用したい式、変数、プレースホルダーにname引数を追加
  • ネットワークを読み込む:tf.train.import_meta_graph(.metaファイル)
    ※tf.train.Saver()の代わりに使うこと。
  • ネットワークを読み込んだ際に、実行するには「”name引数に指定した値”:0」で読み込む
  • tf.get_default_graph().get_tensor_by_name(「”name引数に指定した値”:0」)でpythonの変数として取得が可能。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です