美文网首页
sofasofa——形状识别

sofasofa——形状识别

作者: andyham | 来源:发表于2020-09-15 16:32 被阅读0次

    解决的问题是:给出六千张图像作为训练集。每个图像中只有一个图形,要么是圆形,要么是正方形。
    任务是:根据这六千张图片训练出一个二元分类模型,在测试集上判断每个图像中的形状是圆还是方;测试集中有些图像既不是圆、也不是方,也请将它们甄别出来。
    数据来源:http://sofasofa.io/competition.php?id=9
    参考其他比赛的朋友的文章:
    https://www.jianshu.com/p/1726a1a6f112
    https://blog.csdn.net/weixin_45980783/article/details/108172324?utm_source=app

    目前名次是第2。名次比较靠前,那重点就放在借鉴了各个优秀的博主文章的基础上,如果进一步提高名次的思路。以两个问题贯穿:

    1.数据比赛的基本优化套路是什么?
    2.一些微调的技巧是怎么想?

    1.数据比赛的基本套路是什么?

    如果是传统算法,重点应该是特征工程。如果是神经网络,重点应该是搭建架构。
    而本项目的标杆算法用的是CNN,标杆算法是比较简单的深度学习框架。那么基本套路是特征工程+模型调参。这两步做到极致,应该至少排名能够进入20%。

    1.1 先说特征工程

    特征工程的定义很多。那么我引用一个比较接地气的定义:特征工程就是通过X,创造新的X'。基本的操作包括,衍生(升维),筛选(降维)。
    本例中特征工程包括:降噪,阈值,填充和定义负样本四步。

    第一步:利用中值滤波(median filter)进行降噪.中值滤波的主要原理是将数字图像中的某点用该点的邻域中各个像素值的中值所来代替,例如黑点的像素值比较大,在周围都是白点的情况下,用中值进行填充,那么黑点的像素值就会变小,从而能在过滤出噪声点。标杆模型中用某点周围的5个像素值的中值进行代替。

    median filter
    结果如图所示:
    降噪
    其他几种滤波方式的分类以及分析:https://blog.csdn.net/zaishuiyifangxym/article/details/89788020

    代码如下,值得留意的是:
    (1)linspace:在指定的间隔内返回均匀间隔的数字(第6行)。
    (2)medfilt:中值滤波技术能有效抑制噪声,通过把数字图像中一点的值用该点周围的各点值的中位数来代替,让这些值接近,以消除原图像中的噪声(第11行)。

    import numpy as np
    import  pylab as p
    import scipy.signal as signal
    
    # get some linear data
    x = np.linspace(0,3,101)
    print('Without noisy:',x)
    
    # add some noisy signal
    x[5::10] = 1.5
    print('With noisy:',x)
    plt.plot(x)
    plt.plot(signal.medfilt(x,3))
    plt.plot(signal.medfilt(x,5))
    
    plt.legend(['original signal', 'length 3', 'length 5'])
    plt.show()
    

    第二步:阈值分割法(threshold segmentation)生成掩膜。阈值分割法原理就是以一张图片所有像素值的众数作为阈值,当某点的像素值小于阈值时,则通过布尔值进行分类,分成白点或黑点,例如众数对应是黑点的像素值,那么小于众数的像素值点就被分类为白点,那么一张图片就形成黑白分明的图。如图


    threshold segmentation

    第三步: 利用形态闭合(morphology closing)来填充图中的小洞。
    膨胀:原理是在二值图像上,找到像素值为1的点,将它的邻近像素点都设置成这个值。1值表示白,0值表示黑,因此膨胀操作可以扩大白色值范围,压缩黑色值范围。
    腐蚀:和膨胀相反的操作,将0值扩充到邻近像素。扩大黑色部分,减小白色部分。


    morphology closing

    第二步和第三步的代码参考以下代码,主要是两个if的判断。

    def my_preprocessing(I,show_fig=False):
    
        I_median=ndimage.median_filter(I, size=5)
    
        mask=(I_median<statistics.mode(I_median.flatten()))
    
        I_out=scipy.ndimage.morphology.binary_closing(mask,iterations=2)
    
        if(np.mean(I_out[15:25,15:25].flatten())<0.5):
            I_out=1-I_out
    
        if show_fig:
            fig= plt.figure(figsize=(8, 4))
            plt.gray()
            plt.subplot(2,4,1)
            plt.imshow(I)
            plt.axis('off')
            plt.title('Image')
            
            plt.subplot(2,4,2)
            plt.imshow(I_median)
            plt.axis('off')
            plt.title('Median filter')
    
            plt.subplot(2,4,3)
            plt.imshow(mask)
            plt.axis('off')
            plt.title('Mask')
    
            plt.subplot(2,4,4)
            plt.imshow(I_out)
            plt.axis('off')
            plt.title('Closed mask')
            fig.tight_layout()
            plt.show()
        return I_out
    I_out=my_preprocessing(x_test[6],True);
    
    x_train_prc=np.zeros_like(x_train)
    x_test_prc=np.zeros_like(x_test)
    for i in range(n_train):
        x_train_prc[i]=my_preprocessing(x_train[i])
    for i in range(n_test):
        x_test_prc[i]=my_preprocessing(x_test[i])
    
    

    第四步:增加负样本
    另一位博主的的文章也加入了异形样本且成绩有所提高,所以参考了他的方法。
    https://www.cnblogs.com/wj-1314/p/10512726.html

    负样本

    简单的说,就是往原本的数据里面加了人工标识异型样本。如下:

    def seek_abnormal(testFile, show_fig=True):
        data = pd.read_csv(testFile)
        data1 = np.array(data.iloc[0:, 1:])
        # print(data1.shape)  # (5191, 1600)
        # data2 = np.array(data)
        # data2 = data2[0:, 1:]
        photo1 = np.reshape(data1[119], (40, 40))
        photo2 = np.reshape(data1[956], (40, 40))
        photo3 = np.reshape(data1[973], (40, 40))
        photo4 = np.reshape(data1[974], (40, 40))
        photo5 = np.reshape(data1[988], (40, 40))
    
        if show_fig:
            plt.subplot(1, 5, 1)
            plt.gray()
            plt.imshow(photo1)
            plt.title('photo1')
            plt.subplot(1, 5, 2)
            plt.gray()
            plt.imshow(photo2)
            plt.title('photo2')
            plt.subplot(1, 5, 3)
            plt.gray()
            plt.imshow(photo3)
            plt.title('photo3')
            plt.subplot(1, 5, 4)
            plt.gray()
            plt.imshow(photo4)
            plt.title('photo4')
            plt.subplot(1, 5, 5)
            plt.gray()
            plt.imshow(photo5)
            plt.title('photo5')
    
    

    1.2 说说模型调参

    先用这两个视频对CNN扫个盲先:
    https://www.bilibili.com/video/av838879215?p=8 卷积神经网络原理讲解视频
    https://www.bilibili.com/video/av19765103?p=4 卷积神经网络搭建讲解视频

    流程:
    创建Sequential()对象(简单线性堆叠网络)——
    逐层堆叠网络[Convolution2D-> RELU]卷积层——
    {[Convolution2D-> RELU]卷积层——MaxPooling2D池化层}×3——
    Dropout防止过拟合——
    Flatten压平,多维转为一维——
    [FC -> RELU]全连接层——
    [FC -> Softmax]全连接层

    def built_model(): 
        n_filter=32; 
        model = Sequential() 
        model.add(Convolution2D(filters=n_filter, kernel_size=(5, 5), input_shape=(40, 40, 1), activation='relu'))
        model.add(Convolution2D(filters=n_filter, kernel_size=(5,5), activation='relu'))
        model.add(MaxPooling2D(pool_size=(2, 2)))
        model.add(Convolution2D(filters=n_filter, kernel_size=(5,5), activation='relu'))
        model.add(MaxPooling2D(pool_size=(2, 2)))
        model.add(Convolution2D(filters=n_filter, kernel_size=(3, 3), activation='relu'))
        model.add(MaxPooling2D(pool_size=(2, 2)))
        model.add(Dropout(0.5))
        model.add(Flatten())
        model.add(Dense(units=128, activation='relu'))
        model.add(Dense(3, activation='softmax')) # Final Layer using Softmax
        model.compile(loss='categorical_crossentropy', 
                      optimizer=Adam(lr=0.0003),
                      metrics=['accuracy']) 
        model.summary()
        return model
    
    

    <meta charset="utf-8">

    n_filter就是滤波器(filter)的个数,一般个数从少到多,本文统一为32个
    model.compile()定义损失函数
    model.summary()输出模型各层的参数状况,具体参数计算可参考《详细解释CNN卷积神经网络各层的参数和链接个数的计算》

    模型优化也是三步:训练,调参和选择结构
    第一步:训练模型的同时进行数据增广
    ImageDataGenerator()是keras.preprocessing.image模块中的图片生成器,同时也可以在batch中对数据进行增强,扩充数据集大小,增强模型的泛化能力。比如进行旋转,变形,归一化等等。
    因为异形样本较少,还设置class_weight损失权重,使得损失函数对异形样本更加关注。


    epoch损失图
    datagen = ImageDataGenerator( rotation_range=180, width_shift_range=0.1, height_shift_range=0.1, horizontal_flip=True ) 
    # 训练模型的同时进行数据增广
    
    history=model.fit_generator(datagen.flow(x_train, y_train, batch_size=batch_size),
                                steps_per_epoch=len(x_train) / batch_size, epochs=epochs,
                                class_weight=class_weight, 
                                validation_data=datagen.flow(x_train, y_train, batch_size=batch_size), validation_steps=1)
    

    第二步:调整卷积层和池化层数量
    CNN说简单也不简单,说复杂也不复杂。说不简单,是他的原理的理解和讲解也需要一定的入门门槛。说不复杂是框架一般有人搭建好了,你“可以”依葫芦画瓢。例如现在,葫芦是[Convolution2D-> RELU]卷积层——MaxPooling2D池化层],画瓢是,第一个图是一层,第二图是两层,第三图是三层。


    一层
    两层
    三层

    由上往下,随着卷积层和池化层得增多,时耗增多,这是因为每一层卷积层都会增加参数数量。但发现图2表现最好,即下降得快且又平滑,那就选两层。

    第三步:调参
    主要是调整n_filter个数,
    在上面图的基础上更改n_filter个数(速度较快)n_filter 越多,时耗越多,也是因为参数增多。但n_filter=36 时模型表现最好。


    6
    36
    60

    最终CNN模型决定是:

    from keras.callbacks import TensorBoard
    from keras.layers import Dense, Dropout, MaxPooling2D, Flatten, Convolution2D
    from keras.models import Sequential
    from keras.optimizers import Adam
    from keras import backend as K
    from keras.preprocessing.image import ImageDataGenerator
    import tensorflow.compat.v1 as tf
    
    tf.random.set_random_seed(SEED)
    session_conf = tf.ConfigProto(intra_op_parallelism_threads=1, inter_op_parallelism_threads=1)
    #sess = tf.compat.v1.Session(graph=tf.get_default_graph(), config=session_conf)
    sess = tf.Session(graph=tf.get_default_graph(), config=session_conf)
    #K.set_session(sess)
    tf.keras.backend.set_session(sess)
    def built_model():
        n_filter=32;
        model = Sequential()
        model.add(Convolution2D(filters=n_filter,
                                kernel_size=(5, 5),
                                input_shape=(40, 40, 1),
                                activation='relu'))
        
        model.add(Convolution2D(filters=n_filter,
                                kernel_size=(5,5),
                                activation='relu'))
        
        model.add(MaxPooling2D(pool_size=(2, 2)))
        
        model.add(Convolution2D(filters=n_filter,
                                kernel_size=(5,5),
                                activation='relu'))
        
        model.add(MaxPooling2D(pool_size=(2, 2)))
        
        model.add(Convolution2D(filters=n_filter,
                                kernel_size=(3, 3),
                                activation='relu'))
        
        model.add(MaxPooling2D(pool_size=(2, 2)))
        
        model.add(Dropout(0.5))
        model.add(Flatten())
        model.add(Dense(units=128,
                        activation='relu'))
    
        model.add(Dense(3, activation='softmax'))   # Final Layer using Softmax
    
        model.compile(loss='categorical_crossentropy', 
                      optimizer=Adam(lr=0.0003), 
                      metrics=['accuracy'])  
    
        model.summary()
        return model
    
    def train_model(x_train,y_train, x_test, batch_size=64, epochs=20, model=None,
                    class_weight={0:1.,1:1.,2:10.}):
        if np.ndim(x_train)<4:
            x_train=np.expand_dims(x_train,3)
            x_test=np.expand_dims(x_test,3)
        if model is None:
            model = built_model()
    
    
            datagen = ImageDataGenerator(
                rotation_range=180,
                width_shift_range=0.1,
                height_shift_range=0.1,
                horizontal_flip=True
            )#数据旋转
    
            # 训练模型的同时进行数据增广
            history=model.fit_generator(datagen.flow(x_train, y_train, batch_size=batch_size),
                                steps_per_epoch=len(x_train) / batch_size, epochs=epochs,
                                class_weight=class_weight,
                                validation_data=datagen.flow(x_train, y_train, batch_size=batch_size),
                                validation_steps=1)
    
        print("Loss on training and testing.")
        plt.plot(history.history['loss'], label='train')
        plt.plot(history.history['val_loss'], label='valid')
        plt.legend()
        plt.show()
        pred_prob_train = model.predict(x_train, batch_size=batch_size, verbose=1)
        pred_train = np.array(pred_prob_train > 0.5).astype(int)
        pred_prob_test = model.predict(x_test, batch_size=batch_size, verbose=1)
        pred_test = np.array(pred_prob_test > 0.5).astype(int)
        y_test_hat=pred_test[:,1]+pred_test[:,2]*2;
        y_train_hat=pred_train[:,1]+pred_train[:,2]*2;
        return y_train_hat,y_test_hat,history
    
    

    2.一些微调的技巧是怎么想?

    通常完成上述步骤,基本都拍前列了。如果还想更进一步,那就需要ensemble learning,集成学习。
    集成原理就是少数服从多数,我们采用三个卷积神经网络模型的预测结果进行一个集成,例如样本1在三个模型中的预测结果是一样时,集成结果即三个模型一致的结果,若三个模型中,两个模型的预测结果是一致,而只有一个模型的预测结果不同时,那就少数服从多数,取两个模型一致的预测结果作为集成结果,若三个模型预测结果均不一样时,取最优模型的预测结果作为集成结果。
    集成的思想可以参考:https://www.jianshu.com/p/0313eba19e30

    df1=pd.read_csv('my_CNN_prediction.csv')
    df2=pd.read_csv('3.csv')
    df3=pd.read_csv('4.csv')
    df=pd.merge(df1, df2, on = 'id') 
    df=pd.merge(df,df3,on='id')
    
    df.rename(columns={'y_x':'a','y_y':'b','y':'c'}, inplace=True)
    
    for i in df.index:
        #三个结果相同
        if df.loc[i,'a']+df.loc[i,'b']+df.loc[i,'c']==0 or df.loc[i,'a']+df.loc[i,'b']+df.loc[i,'c']==3 or df.loc[i,'a']+df.loc[i,'b']+df.loc[i,'c']==6:
            df.loc[i,'d']=(df.loc[i,'a']+df.loc[i,'b']+df.loc[i,'c'])/3
            df.loc[i,'f']='三个结果相同'
            df.loc[i,'e']='abc'
            
        #两个结果相同
        elif df.loc[i,'a']==df.loc[i,'b'] and df.loc[i,'b']!=df.loc[i,'c']:
            df.loc[i,'d']=df.loc[i,'a']
            df.loc[i,'f']='两个结果相同'
            df.loc[i,'e']='ab'
        elif df.loc[i,'a']==df.loc[i,'c'] and df.loc[i,'b']!=df.loc[i,'c']:
            df.loc[i,'d']=df.loc[i,'a']
            df.loc[i,'f']='两个结果相同'
            df.loc[i,'e']='ac'
        elif df.loc[i,'b']==df.loc[i,'c'] and df.loc[i,'a']!=df.loc[i,'c']:
            df.loc[i,'d']=df.loc[i,'b']
            df.loc[i,'f']='两个结果相同'
            df.loc[i,'e']='bc'
        #三个结果不相同
        else:
            df.loc[i,'f']='三个结果不相同'
            df.loc[i,'d']=df.loc[i,'b']
    print('-'*125)
    print('列中各元素数量')
    print('-'*125)
    print(df['f'].value_counts() )
    print('-'*125)
    print(df['d'].value_counts() )  
    print('-'*125)
    print(df['e'].value_counts() )  
    

    最终完整代码+数据:
    https://gitee.com/andyham_andy.ham/ML-competition/tree/master/sofa-squre_circle

    相关文章

      网友评论

          本文标题:sofasofa——形状识别

          本文链接:https://www.haomeiwen.com/subject/wmlfyktx.html