U-netのソースコード解説(Keras編)
更新しました。
この企画は2018年8月に書かれたものです。時が経つのは早いもので、もう1年近く経っています。実際、この分野のディープラーニングはハードウェア、ソフトウェアの様々なアップデートが早いですね。
Unetを初めて使うなら、keras版を使うのが結構いいんだけど、結局は自分のニーズがあって、Kerasが面倒になってからその上に自分のモジュールを作っていく、テンソルアライメントとか色々考えないといけないし、Kerasはtensorflowベースだからデバッグも大変だし、今となっては ピトーチ シンプルで柔軟性があるため、広く使われており、発表された論文の多くがPytorchで実装されています、特に最近はtensorflowを追い越す傾向にあります、私もその流れに乗り、昨年Kerasを習得した直後にPytorchに切り替えました、今では私の指導で研究室は基本的にPytorchに切り替えましたのでPytorch習得もお勧めします。以下は私が書いたものです。 Pytorchベースの医用画像セグメンテーションテンプレート このテンプレートは多くのプロジェクトをサポートし、ポータブルで使いやすく、また、Pytorchの学習に関するドキュメントも少しあるので、参考にしてください。皆さん、ありがとうございました
Pytorch_Medical_Segmention_Template : https://github.com/FENGShuanglang/Pytorch_Medical_Segmention_Template
このプロジェクトで実装された機能
<テーブル このプロジェクトファイルでは、以下の機能を実装しています。 1. Unetベースの単一クラスセグメンテーション 2. Nフォールドクロスバリデーションの自動実装 3. 損失関数はDice+BCE 4. SGDの最適化、学習戦略の立案 5. nフォールドチェックポイントファイルの自動保存 6. n倍テンソルボードログの自動保存、複数実験の可視化比較の前後に対応 7. UNetを変更するたびに、比較するために複数の実験の前と後をサポートする UNet フォルダをコピーして、次のように名前を変更します: "UNet_Modify_Content"、そしてこれをベースに変更します。----------------------------------------------------------- split line -------------------------------------------------------------------- ----
まず、論文での説明です。 住所
ソースコードのアドレスです。 https://github.com/FENGShuanglang/unet
環境:python3を使って実行してみてください。python2.7を使ったところ、2日間うまくいかず、テスト出力が常にグレー一色になってしまいました
ソースコードのフォルダディレクトリ。
ここでは、data.py, model.py, main.py(この3つのpythonファイルのみでも可)に注目します。
まずはmain.pyを見て、順番に各関数の意味を探っていきましょう。
from model import *
from data import *# import all functions in these two files
#os.environ["CUDA_VISIBLE_DEVICES"] = "0"
data_gen_args = dict(rotation_range=0.2,
width_shift_range=0.05,
height_shift_range=0.05,
shear_range=0.05,
zoom_range=0.05,
horizontal_flip=True,
fill_mode='nearest')#Dictionary of transformation modes for data enhancement
myGene = trainGenerator(2,'data/membrane/train','image','label',data_gen_args,save_to_dir = None)
# Get a generator that generates enhanced data infinitely at a rate of batch=2
model = unet()
model_checkpoint = ModelCheckpoint('unet_membrane.hdf5', monitor='loss',verbose=1, save_best_only=True)
# callback function, the first is to save the model path, the second is the value of the test, the test Loss is to make it the smallest, the third is to save only the model with the best performance on the validation set
model.fit_generator(myGene,steps_per_epoch=300,epochs=1,callbacks=[model_checkpoint])
#steps_per_epoch refers to how many batch_size per epoch, which is the total number of samples in the training set divided by the value of batch_size
#The above line is using the generator for training the number of batch_size, samples and labels are passed in through myGene
testGene = testGenerator("data/membrane/test")
results = model.predict_generator(testGene,30,verbose=1)
#30 is the step,steps: total number of steps (sample batches) from the generator before stopping. Optional parameter Sequence: if not specified, len(generator) will be used as the number of steps.
The return value of # above is: a Numpy array of predicted values.
saveResult("data/membrane/test",results)#Save results
data.py ファイルです。
from __future__ import print_function
from keras.preprocessing.image import ImageDataGenerator
import numpy as np
import os
import glob
import skimage.io as io
import skimage.transform as trans
sky = [128,128,128]
Building = [128,0,0]
Pole = [192,192,128]
Road = [128,64,128]
Pavement = [60,40,222]
Tree = [128,128,0]
SignSymbol = [192,128,128]
Fence = [64,64,128]
Car = [64,0,128]
Pedestrian = [64,64,0]
Bicyclist = [0,128,192]
Unlabelled = [0,0,0]
COLOR_DICT = np.array([Sky, Building, Pole, Road, Pavement,
Tree, SignSymbol, Fence, Car, Pedestrian, Bicyclist, Unlabelled])
def adjustData(img,mask,flag_multi_class,num_class):
if(flag_multi_class):# This is not a multi-class case in this program, so don't consider this
img = img / 255
mask = mask[:,:,:,0] if(len(mask.shape) == 4) else mask[:,:,0]
#If else is a concise way to write, a line of expressions, true when placed in front, do not understand what the case of mask.shape = 4, because there is a batch_size, so mask will have 3 dimensions [batch_size, wigth, high], I guess mask[:,::, 0] is written wrong, should be written as [0,:,::], so that you can get a piece of the image
new_mask = np.zeros(mask.shape + (num_class,))
# np.zeros is inside the shape tuple, the purpose of this is to extend the data thickness to the num_class layer, in order to achieve one-hot structure in the direction of the layer
for i in range(num_class):
#for one pixel in the image, find the class in mask and convert it into one-hot vector
#index = np.where(mask == i)
#index_mask = (index[0],index[1],index[2],np.zeros(len(index[0]),dtype = np.int64) + i) if (len(mask.shape) == 4) else (index[0],index[1],np. zeros(len(index[0]),dtype = np.int64) + i)
#new_mask[index_mask] = 1
new_mask[mask == i,i] = 1# turn each class of the plane's mask, into a separate layer
new_mask = np.reshape(new_mask,(new_mask.shape[0],new_mask.shape[1]*new_mask.shape[2],new_mask.shape[3])) if flag_multi_class else np. reshape(new_mask,(new_mask.shape[0]*new_mask.shape[1],new_mask.shape[2]))
mask = new_mask
elif(np.max(img) > 1):
img = img / 255
mask = mask /255
mask[mask > 0.5] = 1
mask[mask <= 0.5] = 0
return (img,mask)
# The above function mainly normalizes the pixel values of the data and labels of the training set
def trainGenerator(batch_size,train_path,image_folder,mask_folder,aug_dict,image_color_mode = "grayscale",
mask_color_mode = "grayscale",image_save_prefix = "image",mask_save_prefix = "mask",
flag_multi_class = False,num_class = 2,save_to_dir = None,target_size = (256,256),seed = 1):
'''
can generate image and mask at the same time
use the same seed for image_datagen and mask_datagen to ensure the transformation for image and mask is the same
if you want to visualize the results of generator, set save_to_dir = "your path"
'''
image_datagen = ImageDataGenerator(**aug_dict)
mask_datagen = ImageDataGenerator(**aug_dict)
image_generator = image_datagen.flow_from_directory(#https://blog.csdn.net/nima1994/article/details/80626239
train_path,#training_data_folder_path
classes = [image_folder]
ここで注意すべきは、予測時に直接出力しているので、下のモデルの出力はシグモイド関数後の出力、つまり0と1の間の出力ですが、ここでは0と1の間の数値を直接画像として保存しており、ここで2つの疑問があります。
1. なぜ0と1の間の浮動小数点数を直接画像として保存できるのでしょうか?
なぜなら、skimageモジュールでは、画像データがfloatの場合、その値は0〜1または-1〜1の浮動小数点数であるべきだからです。
2. マスク2値画像を生成せず、直接保存する理由は?
これは、出力データの値がすでに非常に偏った値、つまり0に非常に近い値もあれば1に非常に近い値もあり、その中間の値は非常に少ないので、直接出力しても問題なく、グレースケールマップと同等になります。もし、2値画像を生成したくなったら、次のようにコードを修正すればよいのです。
def saveResult(save_path,npyfile,flag_multi_class = False,num_class = 2):
for i,item in enumerate(npyfile):
if flag_multi_class:
img = labelVisualize(num_class,COLOR_DICT,item)
# multi class then the image is colored, non-multi class (two classes) then it is black and white
else:
img=item[:,:,0]
print(np.max(img),np.min(img))
img[img>0.5]=1#at this point 1 is a floating point number, the following 0 is also
img[img<=0.5]=0
print(np.max(img),np.min(img))
io.imsave(os.path.join(save_path,"%d_predict.png"%i),img)
以下はmodel.pyです。
import numpy as np
import os
import skimage.io as io
import skimage.transform as trans
import numpy as np
from keras.models import *
from keras.layers import *
from keras.optimizers import *
from keras.callbacks import ModelCheckpoint, LearningRateScheduler
from keras import backend as keras
def unet(pretrained_weights = None,input_size = (256,256,1)):
inputs = Input(input_size)
conv1 = Conv2D(64, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(inputs)
conv1 = Conv2D(64, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv1)
pool1 = MaxPooling2D(pool_size=(2, 2))(conv1)
conv2 = Conv2D(128, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(pool1)
conv2 = Conv2D(128, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv2)
pool2 = MaxPooling2D(pool_size=(2, 2))(conv2)
conv3 = Conv2D(256, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(pool2)
conv3 = Conv2D(256, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv3)
pool3 = MaxPooling2D(pool_size=(2, 2))(conv3)
conv4 = Conv2D(512, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(pool3)
conv4 = Conv2D(512, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv4)
drop4 = Dropout(0.5)(conv4)
pool4 = MaxPooling2D(pool_size=(2, 2))(drop4)
conv5 = Conv2D(1024, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(pool4)
conv5 = Conv2D(1024, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv5)
drop5 = Dropout(0.5)(conv5)
up6 = Conv2D(512, 2, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(UpSampling2D(size = (2,2))(drop5))# upsampling followed by convolution, equivalent to transpose the convolution operation!
merge6 = concatenate([drop4,up6],axis=3)
conv6 = Conv2D(512, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(merge6)
conv6 = Conv2D(512, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv6)
up7 = Conv2D(256, 2, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(UpSampling2D(size = (2,2))(conv6))
merge7 = concatenate([conv3,up7],axis = 3)
conv7 = Conv2D(256, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(merge7)
conv7 = Conv2D(256, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv7)
up8 = Conv2D(128, 2, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(UpSampling2D(size = (2,2))(conv7))
merge8 = concatenate([conv2,up8],axis = 3)
conv8 = Conv2D(128, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(merge8)
conv8 = Conv2D(128, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv8)
up9 = Conv2D(64, 2, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(UpSampling2D(size = (2,2))(conv8))
merge9 = concatenate([conv1,up9],axis = 3)
conv9 = Conv2D(64, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(merge9)
conv9 = Conv2D(64, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv9)
conv9 = Conv2D(2, 3, activation = 'relu', padding = 'same', kernel_initializer = 'he_normal')(conv9)
conv10 = Conv2D(1, 1, activation = 'sigmoid')(conv9) # I suspect that this sigmoid activation function is redundant, because it is the binary cross-entropy that is used in the later loss, which contains the sigmoid
model = Model(input = inputs, output = conv10)
model.compile(optimizer = Adam(lr = 1e-4), loss = 'binary_crossentropy', metrics = ['accuracy']) # model must be compiled before execution https://keras-cn.readthedocs.io/ en/latest/getting_started/sequential_model/
#using binary cross-entropy, also known as sigmoid cross-entropy, metrics is generally chosen for accuracy, it will make the accuracy go higher
#model.summary()
if(pretrained_weigh
これで終了です。
テストの結果を見てください:。
1. テストの出力は256*256ですが、入力は512*512であることがわかると思います。これは、入力が一律に、256*256にリサイズされているためです。
2. もう一つの違いは、論文にあるモデル通りに作成されていないことです。具体的な違いとしては、ここでは各畳み込みにpadding=sameを使用していますが、論文ではpaddingされていないため、入力サイズと出力サイズが同じになるのに対し、論文では入力が出力より大きくなっています。具体的な をご覧ください。
ディープラーニングやAIに興味がある方は、私が作ったグループに参加してみてください 825524664 (ディープラーニング交流会)、学習と交流のためだけ、広告なし、よろしくお願いします
オールブラック、オールグレーの解決策。
1. python3 で実行してみる
2.img/255のところを全部img/255.0に変えてみる。
関連
-
undefinedGoogLeNet 論文の翻訳 - 英語と中国語で書かれています。
-
ResNetの紹介
-
py-faster-rcnn/lib の make でエラー: コマンドラインオプション '-Wdate-time' が認識されない
-
xx.exe の 0x00007FF7A7B64FB3 でスローされた例外: 0xC0000005: 場所 0x00 を読み取るアクセス違反
-
[Tensorflow-Error】CUDA_ERROR_OUT_OF_MEMORY:メモリが不足しています。
-
tensorflowをインポートしています。ImportError: libcublas.so.9.0: cannot open shared object file: No such file or director
-
OrderedDict' オブジェクトに 'eval' 属性がありません。
-
参照用シークレットを呼び出す:BN層詳細解説
-
Tensorflowのメタフィジカルエラーです。終了コード -1073741819 (0xC0000005)
-
TensorFlow実行時エラー、AttributeError: モジュール 'pandas' には 'computation' という属性がない。
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
などの警告を出しながらFaster-RCNNを実行します。RuntimeWarning: invalid value encountered in greater_equal などの警告が表示されます。
-
ValueError:入力配列を形状 (450,600,3) から形状 (64,64,3) にブロードキャストできませんでした。
-
Tensorflow 踩坑:ImportError: DLL のロードに失敗しました。指定されたモジュールが見つかりません。 TensorFlowのネイティブランタイムのロードに失敗しました。
-
caffeのインストールで「error : too few arguments in function call」エラーが発生する。
-
Pytorch Deep Learningです。TypeError: 'builtin_function_or_method' object is not iterable エラーの解決方法
-
pytorchはエラーを報告します。ValueError: num_samples は正の整数値であるべきですが、num_samples=0 となりました。
-
tensorflow experience code error Adding visible gpu devices: 0 , モジュール 'tensorflow' には 'Session' という属性がありません。
-
AttributeError: 'tuple' オブジェクトには 'log_softmax' という属性がありません。
-
AttributeError: モジュール 'pandas' には 'core' という属性がありません。
-
tensorflowエラーノート:PyCharmとAttributeErrorの下で様々なモジュールのインポートの問題:モジュール 'pandas.core.computation' は属性を持っていません。