今天算是正式第一次打kaggle比赛,记录一下学习成果。这个比赛是用于检测钢铁是否存在缺陷,给出的训练数据集中有四类的缺陷情形,当然也存在没有划痕的情形。我一个打酱油的先是研读了一份开源的kernel代码,今天对同组的同学的Unet深入的学习的一下,说一下具体思路。训练是采取的两次训练的方法,第一次先是在所有的数据集上进行训练一次,第二次再在全是缺陷的数据集上进行测试;细节方面提一下最后的Loss那里采取的是4个channel的输出,然后对每一个的channel表示一个二分类,采用交叉熵为损失函数。
from keras.layers import Conv2D, MaxPooling2D, Input, BatchNormalization, Deconv2D, Lambda
import keras.backend as K
from keras.models import Model
from keras.optimizers import Adam
import tensorflow as tf
def downsampling_conv_block(net, filters, kernel=3):#U-net下采样部分
net = Conv2D(filters, kernel, padding='same', activation='relu')(net)
net = BatchNormalization()(net)
net = Conv2D(filters, kernel, padding='same', activation='relu')(net)
_net = BatchNormalization()(net)
net = MaxPooling2D()(_net)
return net, _net#_net是为了后面的跳跃连接
# keras 搭建网络时,只可以使用keras类型的变量,若要用一些tf的操作则需考虑使用Lambda层 如下所示,其中K为调用keras的底层,常用如tf、theano、mxnet等
def upsampling_conv_block(net1, net2, filters, kernel=3):
net1 = Deconv2D(filters, kernel, strides=2, padding='same', activation='relu')(net1)
net = Lambda(lambda x: K.concatenate(x, -1))([net1, net2])
net = Conv2D(filters, kernel, padding='same', activation='relu')(net)
net = BatchNormalization()(net)
net = Conv2D(filters, kernel, padding='same', activation='relu')(net)
net = BatchNormalization()(net)
return net
# 在搭建工程时,网络常常用类搭建,本程序为自娱自乐,用函数节省时间
def unet(istraining=True):
input = Input(shape=(256, 512, 1))
net1, _net1 = downsampling_conv_block(input, 32)
net2, _net2 = downsampling_conv_block(net1, 64)
net3, _net3 = downsampling_conv_block(net2, 128)
net4, _net4 = downsampling_conv_block(net3, 256)
net5 = Conv2D(512, 3, padding='same', activation='relu')(net4)
net5 = BatchNormalization()(net5)
net5 = Conv2D(256, 1, activation='relu')(net5)
net5 = BatchNormalization()(net5)
net5 = Conv2D(256, 3, padding='same', activation='relu')(net5)
net5 = BatchNormalization()(net5)
net5 = Conv2D(512, 1, activation='relu')(net5)
net5 = BatchNormalization()(net5)
net6 = upsampling_conv_block(net5, _net4, 256)
net7 = upsampling_conv_block(net6, _net3, 128)
net8 = upsampling_conv_block(net7, _net2, 64)
net9 = upsampling_conv_block(net8, _net1, 32)
output = Conv2D(4, 1, padding='same', activation='sigmoid')(net9)
model = Model(inputs=input, outputs=output)
if istraining:
compile_model(model)
return model
def focal_loss(gamma=2.):
def focal_loss_fixed(y_true, y_pred):
eps = 0.0001
pt_1 = tf.where(tf.equal(y_true, 1), y_pred, tf.ones_like(y_pred))#预测值和真实值都是1
pt_0 = tf.where(tf.equal(y_true, 0), y_pred, tf.zeros_like(y_pred))#预测值和真实值都是0
return -K.mean(K.pow(1 - pt_1, gamma) * K.log(pt_1 + eps)) - K.mean(K.pow(pt_0, gamma) * K.log(1 - pt_0 + eps))
return focal_loss_fixed
def dice_coefficient(y_pre, y_true):
eps = 0.0001
y_pre = tf.where(y_pre > 0.5, K.ones_like(y_pre), K.zeros_like(y_pre))
# int_sec = K.sum(y_pre * y_true, [1, 2, 3])
# xy_sum = K.sum(y_true, [1, 2, 3]) + K.sum(y_pre, [1, 2, 3])
int_sec = K.sum(y_pre * y_true)
xy_sum = K.sum(y_true) + K.sum(y_pre)
return (2 * int_sec + eps) / (xy_sum + eps)
# loss可以使用keras.losses里自带的loss,也可以如上自己定义,但是损失函数默认两个参数必须为y_true和y_pred,若欲使用其他参数
# 可以如上focal_loss函数嵌套的方法调用,metrics也同理
def compile_model(model):
opt = Adam(lr=0.0005)
model.compile(opt, focal_loss(), metrics=[dice_coefficient])
import cv2
import os
import numpy as np
import pandas as pd
def read_data(path):
data = pd.read_csv(path)
img_ids, img_masks = data['ImageId_ClassId'], data['EncodedPixels']
idx = list(range(0, len(img_ids), 4))
img_ids = img_ids[idx]#arry直接传入一个list可以直接取出这个List位置下的元素。
img_ids = np.array([s[:-2] for s in img_ids])#获得每一张图片的名字
img_masks = np.array(img_masks)#取得所有的mask,大小为img_ids的四倍,转化为array
img_masks = np.reshape(img_masks, [-1, 4])#转成二维的,参数[-1,4]表示第二个维度为4,第一个维度用总的/4
# print(img_masks)
return img_ids, img_masks
def split_nan(id, mask):
nor_id, nan_id, nor_mask, nan_mask = [], [], [], []
for i in range(len(mask)):#mask 是一个二维的,len()取第一个维度
zm = 0
for m in mask[i]:
if isinstance(m, float):
zm += 1
if zm == 4:
nan_id.append(id[i])
nan_mask.append(mask[i])# 一张图假如四中类型的缺陷不存在的话就存在nan_maks
else:
nor_id.append(id[i])
nor_mask.append(mask[i]) #nor_mask用于储存那些一张图上大于或者等于1缺陷的
nor_id = np.array(nor_id)
nan_id = np.array(nan_id)
nor_mask = np.array(nor_mask)
nan_mask = np.array(nan_mask)
return nor_id, nan_id, nor_mask, nan_mask
def split_all(id,mask):
data_all, data_mask = [], []
for i in range(len(mask)):
data_mask.append(mask[i])
data_all.append(id[i])
data_mask = np.array(data_mask)
data_all = np.array(data_all)
return data_all, data_mask
def generator(img, mask, batchsize=16):
batchnum = len(img) // batchsize #// 表示整数除法,在batch_size为16的时候有多少个batch.
idx = list(range(len(img)))#图片个数同size的List[0,1,2,...,size-1]
while True:
np.random.shuffle(idx) #对Train的数据顺序进行打乱
for i in range(batchnum):
l = i * batchsize
r = l + batchsize
batchimg, batchmask = img[idx[l:r]], mask[idx[l:r]] #按照给出的batch_size取一个batch的图片
batchimg = read_img(batchimg)#读取这个patch的照片
batchmask = read_mask(batchmask)#读取对应的mask
yield batchimg, batchmask #递归的输出值,每一次都会返回得到的值,用于在for,while中输出每一次迭代的值
def read_img(paths):#这个path是个array,表示一个batch的照片名
imgs = []
for s in paths:
img = cv2.imread(os.path.join('../severstal-steel-defect-detection/train_images', s), cv2.IMREAD_GRAYSCALE)#以灰度的方式读取,不包括a通道
img = cv2.resize(img, (512, 256))
imgs.append(img)
imgs = np.array(imgs)# shape=(b,h,w,c)??
imgs = imgs / 255
imgs = np.expand_dims(imgs, -1)#在最后的一个维度加入一个维度??
return imgs
def read_mask(masks):
ms = []
for m4 in masks:#m4表示每一行,表示一个照片的Mask个数,4个数据,可能为空
mm = []
for m in m4: #m是这一行的的数据,表示四个class
if isinstance(m, float):#没有缺陷
tmp = np.zeros((256, 512, 1))#生成一个whc直接为训练的数据大小,值为0的tensor,表示没有缺陷
else:
# tmp = pix2mask(m)
tmp = rle2mask(m) #存在缺陷。返回一个mask
# print(tmp.shape)
mm.append(tmp)#储存4个class的Mask
ms.append(np.concatenate(mm, -1))#在最后一个维度上组合成一个tensor
ms = np.array(ms)#一张img此时就只对应一个ms了
return ms
def rle2mask(mask_rle, shape=(1600, 256)):
s = mask_rle.split()
#np.asarray()和np.array()一样转化为np能处理的数据,但是除非必要,是不会cope对象的而np.array()是会cope对象的
starts, lengths = [np.asarray(x, dtype=int) for x in (s[0:][::2], s[1:][::2])]
#上式表示取mask中起点和每一个起点的步长
starts -= 1
ends = starts + lengths#对每一个start都存在一个end
img = np.zeros(shape[0]*shape[1], dtype=np.uint8)
for lo, hi in zip(starts, ends): #zip函数打包合成数据,一个start对应一个end
img[lo:hi] = 1#该处的mask值为1
img = img.reshape(shape).T#旋转shape变为(1600,512)
img = cv2.resize(img, (512, 256))#resize()这一步默认采用双线性插值,会改变灰度值
img = np.where(img >= 0.5, np.ones_like(img), np.zeros_like(img))#将上一步的操作的灰度值从新变成0,1
img = np.reshape(img, (256, 512, 1))
return img
def mask2rle(img):
'''
img: numpy array, 1 - mask, 0 - background
Returns run length as string formated
'''
pixels = img.T.flatten()#旋转拉直
pixels = np.concatenate([[0], pixels, [0]])
runs = np.where(pixels[1:] != pixels[:-1])[0] + 1
runs[1::2] -= runs[::2]
return ' '.join(str(x) for x in runs)
def pix2mask(pix):
mask = np.zeros(256 * 1600)
pix = np.reshape(pix.split(), [-1, 2])
pix = pix.astype(np.int32)
for s, l in pix:
s = s - 1
mask[s:(s + l)] = 255
mask = np.reshape(mask, (256, 1600))
mask = cv2.resize(mask, (512, 256))
mask = np.reshape(mask, (256, 512, 1))
mask[np.where(mask >= 127.5)] = 1
mask[np.where(mask < 127.5)] = 0
return mask
from model import *
from data import *
from sklearn.model_selection import train_test_split
from keras.callbacks import ModelCheckpoint
epochs = 30
batchsize = 16
is_mining_train = True
path = '../severstal-steel-defect-detection/train.csv'
data, label = read_data(path)
if is_mining_train:
#data, _, label, _ = split_nan(data, label)#这里只取了有缺陷的做训练
data,label=split_all(data,label)#取所有的数据进行第一次的测试
#train_test_split函数用于将矩阵随机划分为训练子集和测试子集,并返回划分好的训练集测试集样本和训练集测试集标签。
td, vd, tl, vl = train_test_split(data, label, test_size=0.2)#td,vd用于训练,20%的tl和vl用于测试
tg = generator(td, tl, batchsize)
vg = generator(vd, vl, batchsize)
net = unet()
#net.load_weights('./base_models/ver1.hdf5')
save_path = './models/{epoch:02d}-{val_loss:.2f}.hdf5'#参数什么意思?如何保存的name为ver2
ckpt = ModelCheckpoint(save_path)
# 网络训练常用fit或fit_genertaor,两者区别在于前者必须将数据全部读入内存,这在实际应用中很少能做到,所以常用后者,后者需要传入一个generator
# generator函数参照python的yield用法,另外,若不方便使用划分epoch,则可以用for循环和train_on_batch训练
# callbacks回调个人常用ModelCheckpoint和EarlyStopping或继承CallBack实现一些自己的功能
net.fit_generator(tg, len(td) // batchsize, epochs, callbacks=[ckpt], validation_data=vg,
validation_steps=len(vd) // batchsize)
我这里是只采用了第一次的训练的train代码。
作为新手,我还是有几个问题需要问:
最后,今天七夕,大家玩的快乐!