• 猫狗识别


    import os
    
    import tensorflow.compat.v1 as tf
    tf.disable_v2_behavior()
    from PIL import Image
    import matplotlib.pyplot as plt
    import numpy as np
    import cv2
    def get_files(file_dir):
        cats=[] #猫的图片 列表    
        lable_cats=[] #猫的标签 列表    
        dogs=[] #狗的图片 列表    
        lable_dogs=[]  #狗的标签 列表     
        
        #os.listdir为列出路径内的所有文件    
        for file in os.listdir(file_dir):        
            name = file.split('.')   #将每一个文件名都进行分割,以.分割       
            #这样文件名 就变成了三部分 name的形式 [‘dog’,‘9981’,‘jpg’]        
            if name[0]=='cat':            
                cats.append(file_dir+file)   
                #在定义的cats列表内添加图片路径,由文件夹的路径+文件名组成            
                lable_cats.append(0) #在猫的标签列表中添加对应图片的标签,猫的标签为0,狗为1        
            else:            
                dogs.append(file_dir+file)            
                lable_dogs.append(1)    
        print('There are %d cats\nThere are %d dogs' %(len(cats), len(dogs)))   #打印猫和狗的数量    
            
        image_list = np.hstack((cats, dogs))  #将猫和狗的列表合并为一个列表    
        label_list = np.hstack((lable_cats, lable_dogs)) #将猫和狗的标签列表合并为一个列表     
                
        #将两个列表构成一个数组    
        temp=np.array([image_list,label_list])    
        temp=temp.transpose() #将数组矩阵转置    
        np.random.shuffle(temp) #将数据打乱顺序,不再按照前边全是猫,后面全是狗这样排列     
        image_list=list(temp[:,0]) #图片列表为temp 数组的第一个元素    
        label_list = list(temp[:, 1]) #标签列表为temp数组的第二个元素    
        label_list = [int(i) for i in label_list] #转换为int类型    
        #返回读取结果,存放在image_list,和label_list中    
        return image_list, label_list
    def get_batch(image,label,image_W,image_H,batch_size,capacity):    
        #数据转换    
        image = tf.cast(image, tf.string)   #将image数据转换为string类型    
        label = tf.cast(label, tf.int32)    #将label数据转换为int类型    
        #入队列    
        input_queue = tf.train.slice_input_producer([image, label])    
        #取队列标签 张量    
        label = input_queue[1]     
        #取队列图片 张量    
        image_contents = tf.read_file(input_queue[0])     
        #解码图像,解码为一个张量    
        image = tf.image.decode_jpeg(image_contents, channels=3)     
        #对图像的大小进行调整,调整大小为image_W,image_H    
        image = tf.image.resize_image_with_crop_or_pad(image, image_W, image_H)    
        #对图像进行标准化    
        image = tf.image.per_image_standardization(image) 
        
        #等待出队    
        image_batch, label_batch = tf.train.batch([image, label],                               
                                                  batch_size= batch_size,                                                
                                                  num_threads= 64,                                                 
                                                  capacity = capacity)     
        label_batch = tf.reshape(label_batch, [batch_size]) #将label_batch转换格式为[]    
        image_batch = tf.cast(image_batch, tf.float32)   #将图像格式转换为float32类型      
        return image_batch, label_batch  #返回所处理得到的图像batch和标签batch
    import matplotlib.pyplot as plt
     
    BATCH_SIZE = 2
    CAPACITY = 256
    IMG_W = 208
    IMG_H = 208
     
    train_dir = './dataset/dc/train/'
    image_list, label_list = get_files(train_dir)   #读取数据和标签
    image_batch, label_batch = get_batch(image_list, label_list, IMG_W, IMG_H, BATCH_SIZE, CAPACITY)    #将图片分批次
     
    #开启会话,使用try--except--finally结构来执行队列操作
    with tf.Session() as sess:
        i = 0
        coord = tf.train.Coordinator()
        threads = tf.train.start_queue_runners(coord=coord)
     
        try:
            while not coord.should_stop() and i<2:
     
                img, label = sess.run([image_batch, label_batch])
     
                # just test one batch
                for j in np.arange(BATCH_SIZE):
                    print('label: %d' %label[j])  #j-index of quene of Batch_size
                    plt.imshow(img[j,:,:,:])
                    plt.show()
                i+=1
     
        except tf.errors.OutOfRangeError:
            print('done!')
        finally:
            coord.request_stop()
        coord.join(threads)
    def inference(images, batch_size, n_classess):        
        """    
        第一个卷积层    
        """    
        # tf.variable_scope() 主要结合 tf.get_variable() 来使用,实现变量共享。下次调用不用重新产生,这样可以保存参数    
        with tf.variable_scope('conv1') as scope:         #初始化权重,[3,3,3,16]        
            weights = tf.get_variable('weights', shape = [3, 3, 3, 16], dtype = tf.float32,                                  
                                      initializer = tf.truncated_normal_initializer(stddev=0.1, dtype=tf.float32))         
            #初始化偏置,16个        
            biases = tf.get_variable('biases', shape=[16], dtype = tf.float32,                                 
                                     initializer = tf.constant_initializer(0.1))        
            conv = tf.nn.conv2d(images, weights, strides=[1,1,1,1], padding='SAME')                
            # 将偏置加在所得的值上面        
            pre_activation = tf.nn.bias_add(conv, biases)        
            # 将计算结果通过relu激活函数完成去线性化        
            conv1 = tf.nn.relu(pre_activation, name= scope.name)     
            
        """    
        池化层    
        """    
        with tf.variable_scope('pooling1_lrn') as scope:        
            # tf.nn.max_pool实现了最大池化层的前向传播过程,参数和conv2d类似,ksize过滤器的尺寸        
            pool1 = tf.nn.max_pool(conv1, ksize=[1,3,3,1],strides=[1,2,2,1],padding='SAME',name='poolong1')        
            # 局部响应归一化(Local Response Normalization),一般用于激活,池化后的一种提高准确度的方法。        
            norm1 = tf.nn.lrn(pool1, depth_radius=4, bias=1, alpha=0.001/9.0, beta=0.75, name='norm1')     
            
            
        """    
        第二个卷积层    
        """    
        # 计算过程和第一层一样,唯一区别为命名空间    
        with tf.variable_scope('conv2') as scope:        
            weights = tf.get_variable('weights', shape=[3,3,16,16], dtype=tf.float32, initializer=tf.truncated_normal_initializer(stddev=0.1,dtype=tf.float32))        
            biases = tf.get_variable('biases',                                 
                                     shape=[16],                                  
                                     dtype=tf.float32,                                 
                                     initializer=tf.constant_initializer(0.1))        
            conv = tf.nn.conv2d(norm1, weights, strides=[1,1,1,1],padding='SAME')        
            pre_activation = tf.nn.bias_add(conv, biases)        
            conv2 = tf.nn.relu(pre_activation, name='conv2')        
            
        
        """    
        第二池化层    
        """    
        with tf.variable_scope('pooling2_lrn') as scope:        
            norm2 = tf.nn.lrn(conv2, depth_radius=4,bias=1,alpha=0.001/9,beta=0.75,name='norm2')        
            pool2 = tf.nn.max_pool(norm2, ksize=[1,3,3,1],strides=[1,1,1,1],padding='SAME',name='pooling2')            
            
            
        """     
        local3 全连接层    
        """    
        with tf.variable_scope('local3') as scope:        
            # -1代表的含义是不用我们自己指定这一维的大小,函数会自动计算        
            reshape = tf.reshape(pool2, shape=[batch_size, -1])        
            # 获得reshape的列数,矩阵点乘要满足列数等于行数        
            dim = reshape.get_shape()[1].value        
            weights = tf.get_variable('weights', shape=[dim,128],dtype=tf.float32,initializer=tf.truncated_normal_initializer(stddev=0.005,dtype=tf.float32))        
            biases = tf.get_variable('biases',shape=[128], dtype=tf.float32,initializer=tf.constant_initializer(0.1))        
            local3 = tf.nn.relu(tf.matmul(reshape, weights) + biases, name=scope.name)        
         
        
        """     
        local4 全连接层    
        """    
        with tf.variable_scope('local4') as scope:        
            weights = tf.get_variable('weights',shape=[128,128],dtype=tf.float32,initializer=tf.truncated_normal_initializer(stddev=0.005,dtype=tf.float32))        
            biases = tf.get_variable('biases', shape=[128],dtype=tf.float32, initializer=tf.constant_initializer(0.1))        
            local4 = tf.nn.relu(tf.matmul(local3,weights) + biases, name = 'local4')    
        
        
        """     
        lsoftmax逻辑回归 
        将前面的全连接层输出,做一个线性回归,计算出每一类的得分,在这里是2类
        所以这个层输出的是2个得分
        """    
        with tf.variable_scope('softmax_linear') as scope:        
            weights = tf.get_variable('softmax_linear',shape=[128, n_classess],dtype=tf.float32,initializer=tf.truncated_normal_initializer(stddev=0.005,dtype=tf.float32))        
            biases = tf.get_variable('biases',shape=[n_classess],dtype=tf.float32,initializer=tf.constant_initializer(0.1))        
            softmax_linear = tf.add(tf.matmul(local4, weights),biases,name='softmax_linear')            
        return softmax_linear
    def losses(logits, labels):    
        with tf.variable_scope('loss') as scope:        
            # 计算使用了softmax回归后的交叉熵损失函数        
            # logits表示神经网络的输出结果,labels表示标准答案        
            cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits, labels=labels,name='xentropy_per_example')        
            # 求cross_entropy所有元素的平均值        
            loss = tf.reduce_mean(cross_entropy, name='loss')        
            # 对loss值进行标记汇总,一般在画loss, accuary时会用到这个函数。        
            tf.summary.scalar(scope.name+'/loss',loss)    
        return loss 
    def trainning(loss, learning_rate):    
        with tf.name_scope('optimizer'):        
            # 在训练过程中,先实例化一个优化函数,比如tf.train.GradientDescentOptimizer,并基于一定的学习率进行梯度优化训练        
            optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)        
            # 设置一个用于记录全局训练步骤的单值        
            global_step = tf.Variable(0, name='global_step',trainable=False)        
            # 添加操作节点,用于最小化loss,并更新var_list,返回为一个优化更新后的var_list,如果global_step非None,该操作还会为global_step做自增操作        
            train_op = optimizer.minimize(loss, global_step=global_step)    
        return train_op
    def evaluation(logits, labels):    
        with tf.variable_scope('accuracy') as scope:        
            correct = tf.nn.in_top_k(logits,labels,1)    
            # 计算预测的结果和实际结果的是否相等,返回一个bool类型的张量        
            # K表示每个样本的预测结果的前K个最大的数里面是否含有target中的值。一般都是取1。        
            # 转换类型        
            correct = tf.cast(correct, tf.float16)        
            accuracy = tf.reduce_mean(correct)             
            #取平均值,也就是准确率        
            # 对准确度进行标记汇总        
            tf.summary.scalar(scope.name+'/accuracy',accuracy)    
        return accuracy
    import os
    import numpy as np
    import tensorflow.compat.v1 as tf
    tf.disable_v2_behavior()
    from PIL import Image  
    import matplotlib.pyplot as plt  
    N_CLASSES = 2 # 二分类问题,只有是还是否,即0,1
    IMG_W = 208 #图片的宽度
    IMG_H = 208 #图片的高度
    BATCH_SIZE = 16 #批次大小
    CAPACITY = 2000  # 队列最大容量2000
    MAX_STEP = 5000 #最大训练步骤
    learning_rate = 0.0001  #学习率
    def run_training():
        """    
        ##1.数据的处理    
        """    
        # 训练图片路径    
        train_dir = './dataset/dc/train/'    
        # 输出log的位置    
        logs_train_dir = './model/'     
        # 模型输出    
        train_model_dir = './model/'
        # 获取数据中的训练图片 和 训练标签    
        train, train_label = get_files(train_dir)     
        # 获取转换的TensorFlow 张量    
        train_batch, train_label_batch = get_batch(train,train_label,IMG_W,IMG_H,BATCH_SIZE,CAPACITY)     
        
        
        """    
        ##2.网络的推理    
        """    
        # 进行前向训练,获得回归值    
        train_logits = inference(train_batch, BATCH_SIZE, N_CLASSES)     
        
        """    
        ##3.定义交叉熵和 要使用的梯度下降的 优化器     
        """    
        # 计算获得损失值loss    
        train_loss = losses(train_logits, train_label_batch)    
        # 对损失值进行优化    
        train_op = trainning(train_loss, learning_rate)     
        
        """    
        ##4.定义后面要使用的变量    
        """    
        # 根据计算得到的损失值,计算出分类准确率    
        train__acc = evaluation(train_logits, train_label_batch) 
        # 将图形、训练过程合并在一起    
        summary_op = tf.summary.merge_all()   
        
        
        # 新建会话    
        sess = tf.Session()      
        
        
        # 将训练日志写入到logs_train_dir的文件夹内    
        train_writer = tf.summary.FileWriter(logs_train_dir, sess.graph)    
        saver = tf.train.Saver()  # 保存变量     
        
        # 执行训练过程,初始化变量    
        sess.run(tf.global_variables_initializer())    
        
        
        # 创建一个线程协调器,用来管理之后在Session中启动的所有线程    
        coord = tf.train.Coordinator()    
        # 启动入队的线程,一般情况下,系统有多少个核,就会启动多少个入队线程(入队具体使用多少个线程在tf.train.batch中定义);    
        threads = tf.train.start_queue_runners(sess=sess, coord=coord)     
        
        """    
        进行训练:    
        使用 coord.should_stop()来查询是否应该终止所有线程,当文件队列(queue)中的所有文件都已经读取出列的时候,    
        会抛出一个 OutofRangeError 的异常,这时候就应该停止Sesson中的所有线程了;    
        """    
        
        try:        
            for step in np.arange(MAX_STEP): #从0 到 2000 次 循环            
                if coord.should_stop():                
                    break            
                _, tra_loss, tra_acc = sess.run([train_op, train_loss, train__acc])               
                
                # 每50步打印一次损失值和准确率            
                if step % 50 == 0:                
                    print('Step %d, train loss = %.2f, train accuracy = %.2f%%' % (step, tra_loss, tra_acc * 100.0)) 
                    
                    summary_str = sess.run(summary_op)                
                    train_writer.add_summary(summary_str, step)              
                
                
                # 每2000步保存一次训练得到的模型            
                if step % 2000 == 0 or (step + 1) == MAX_STEP:                
                    checkpoint_path = os.path.join(train_model_dir, 'model.ckpt')                
                    saver.save(sess, checkpoint_path, global_step=step)      
                    
        # 如果读取到文件队列末尾会抛出此异常    
        except tf.errors.OutOfRangeError:        
            print('Done training -- epoch limit reached')    
        finally:        
            coord.request_stop()       
            # 使用coord.request_stop()来发出终止所有线程的命令  
            
        coord.join(threads)            # coord.join(threads)把线程加入主线程,等待threads结束    
        sess.close()                   # 关闭会话
    import tensorflow.compat.v1 as tf
    tf.disable_v2_behavior()
    import numpy as np
    import math
    from PIL import Image
    import matplotlib.pyplot as plt
    import tempfile
     
    # 获取一张图片
    def get_one_image(data):     
        n = len(data)      
        #训练集长度    
        ind = np.random.randint(0, n)   #生成随机数    
        img_dir = data[ind]    #从训练集中提取选中的图片 
        
        image = Image.open(img_dir)       
        plt.imshow(image)   #显示图片  
        plt.show()
        image = image.resize([208, 208])    
        image = np.array(image)    
        return image   
     
    def get_one_image_file(img_dir):        
        image = Image.open(img_dir)       
        plt.imshow(image)   #显示图片  
        plt.show()
        image = image.resize([208, 208])    
        image = np.array(image)    
        return image  
    def evaluate_one_image():     
        # 数据集路径    
        
        image_array=get_one_image_file("./dataset/dc/test/38.jpg") 
        
        with tf.Graph().as_default():        
            BATCH_SIZE = 1   # 获取一张图片        
            N_CLASSES = 2  #二分类         
            image = tf.cast(image_array, tf.float32)        
            image = tf.image.per_image_standardization(image)        
            image = tf.reshape(image, [1, 208, 208, 3])     #inference输入数据需要是4维数据,需要对image进行resize        
            logit = inference(image, BATCH_SIZE, N_CLASSES)   
            logit = tf.nn.softmax(logit)    #inference的softmax层没有激活函数,这里增加激活函数         
            
            #因为只有一副图,数据量小,所以用placeholder        
            x = tf.placeholder(tf.float32, shape=[208, 208, 3])         
            #         
            # 训练模型路径        
            logs_train_dir = './model/'         
            saver = tf.train.Saver()         
            with tf.Session() as sess:             
                # 从指定路径下载模型            
                print("Reading checkpoints...")            
                ckpt = tf.train.get_checkpoint_state(logs_train_dir)             
                if ckpt and ckpt.model_checkpoint_path:                                
                    global_step = ckpt.model_checkpoint_path.split('/')[-1].split('-')[-1]                
                    saver.restore(sess, ckpt.model_checkpoint_path)                 
                    print('Loading success, global_step is %s' % global_step)            
                else:                
                    print('No checkpoint file found')             
                prediction = sess.run(logit, feed_dict={x: image_array})            
                # 得到概率最大的索引            
                max_index = np.argmax(prediction)                  
                if max_index==0:                
                    print('This is a cat with possibility %.6f' %prediction[:, 0])            
                else:                
                    print('This is a dog with possibility %.6f' %prediction[:, 1])    
    def main():
        run_training()#训练模型
        evaluate_one_image()
     
    if __name__ == '__main__':
        main()

    训练模型大概训练了半个小时左右,结果判断了一次发现,结果不是很理想,说明模型还有待优化

    Step 0, train loss = 0.69, train accuracy = 43.75% Step 50, train loss = 0.69, train accuracy = 50.00% Step 100, train loss = 0.69, train accuracy = 43.75% Step 150, train loss = 0.70, train accuracy = 43.75% Step 200, train loss = 0.68, train accuracy = 56.25% Step 250, train loss = 0.64, train accuracy = 68.75% Step 300, train loss = 0.64, train accuracy = 68.75% Step 350, train loss = 0.72, train accuracy = 56.25% Step 400, train loss = 0.65, train accuracy = 62.50% Step 450, train loss = 0.60, train accuracy = 68.75% Step 500, train loss = 0.72, train accuracy = 43.75% Step 550, train loss = 0.58, train accuracy = 75.00% Step 600, train loss = 0.60, train accuracy = 75.00% Step 650, train loss = 0.72, train accuracy = 56.25% Step 700, train loss = 0.61, train accuracy = 62.50% Step 750, train loss = 0.59, train accuracy = 56.25% Step 800, train loss = 0.59, train accuracy = 68.75% Step 850, train loss = 0.63, train accuracy = 75.00% Step 900, train loss = 0.81, train accuracy = 43.75% Step 950, train loss = 0.55, train accuracy = 75.00% Step 1000, train loss = 0.68, train accuracy = 62.50% Step 1050, train loss = 0.77, train accuracy = 56.25% Step 1100, train loss = 0.67, train accuracy = 62.50% Step 1150, train loss = 0.48, train accuracy = 81.25% Step 1200, train loss = 0.61, train accuracy = 56.25% Step 1250, train loss = 0.64, train accuracy = 56.25% Step 1300, train loss = 0.67, train accuracy = 56.25% Step 1350, train loss = 0.58, train accuracy = 62.50% Step 1400, train loss = 0.70, train accuracy = 62.50% Step 1450, train loss = 0.60, train accuracy = 75.00% Step 1500, train loss = 0.74, train accuracy = 50.00% Step 1550, train loss = 0.71, train accuracy = 68.75% Step 1600, train loss = 0.56, train accuracy = 68.75% Step 1650, train loss = 0.49, train accuracy = 81.25% Step 1700, train loss = 0.58, train accuracy = 75.00% Step 1750, train loss = 0.53, train accuracy = 75.00% Step 1800, train loss = 0.61, train accuracy = 56.25% Step 1850, train loss = 0.76, train accuracy = 50.00% Step 1900, train loss = 0.58, train accuracy = 68.75% Step 1950, train loss = 0.58, train accuracy = 75.00% Step 2000, train loss = 0.50, train accuracy = 75.00% Step 2050, train loss = 0.53, train accuracy = 75.00% Step 2100, train loss = 0.50, train accuracy = 81.25% Step 2150, train loss = 0.71, train accuracy = 62.50% Step 2200, train loss = 0.51, train accuracy = 81.25% Step 2250, train loss = 0.60, train accuracy = 56.25% Step 2300, train loss = 0.41, train accuracy = 81.25% Step 2350, train loss = 0.52, train accuracy = 81.25% Step 2400, train loss = 0.54, train accuracy = 68.75% Step 2450, train loss = 0.73, train accuracy = 62.50% Step 2500, train loss = 0.41, train accuracy = 81.25% Step 2550, train loss = 0.56, train accuracy = 68.75% Step 2600, train loss = 0.64, train accuracy = 68.75% Step 2650, train loss = 0.60, train accuracy = 62.50% Step 2700, train loss = 0.59, train accuracy = 68.75% Step 2750, train loss = 0.60, train accuracy = 68.75% Step 2800, train loss = 0.53, train accuracy = 62.50% Step 2850, train loss = 0.54, train accuracy = 75.00% Step 2900, train loss = 0.52, train accuracy = 75.00% Step 2950, train loss = 0.66, train accuracy = 56.25% Step 3000, train loss = 0.43, train accuracy = 87.50% Step 3050, train loss = 0.44, train accuracy = 81.25% Step 3100, train loss = 0.67, train accuracy = 56.25% Step 3150, train loss = 0.73, train accuracy = 62.50% Step 3200, train loss = 0.43, train accuracy = 93.75% Step 3250, train loss = 0.48, train accuracy = 75.00% Step 3300, train loss = 0.58, train accuracy = 75.00% Step 3350, train loss = 0.64, train accuracy = 68.75% Step 3400, train loss = 0.52, train accuracy = 68.75% Step 3450, train loss = 0.60, train accuracy = 68.75% Step 3500, train loss = 0.47, train accuracy = 75.00% Step 3550, train loss = 0.53, train accuracy = 75.00% Step 3600, train loss = 0.42, train accuracy = 81.25% Step 3650, train loss = 0.50, train accuracy = 68.75% Step 3700, train loss = 0.38, train accuracy = 87.50% Step 3750, train loss = 0.45, train accuracy = 81.25% Step 3800, train loss = 0.32, train accuracy = 93.75% Step 3850, train loss = 0.52, train accuracy = 68.75% Step 3900, train loss = 0.39, train accuracy = 81.25% Step 3950, train loss = 0.32, train accuracy = 81.25% Step 4000, train loss = 0.42, train accuracy = 87.50% Step 4050, train loss = 0.60, train accuracy = 56.25% Step 4100, train loss = 0.59, train accuracy = 68.75% Step 4150, train loss = 0.45, train accuracy = 75.00% Step 4200, train loss = 0.53, train accuracy = 81.25% Step 4250, train loss = 0.45, train accuracy = 75.00% Step 4300, train loss = 0.42, train accuracy = 81.25% Step 4350, train loss = 0.46, train accuracy = 68.75% Step 4400, train loss = 0.55, train accuracy = 62.50% Step 4450, train loss = 0.63, train accuracy = 81.25% Step 4500, train loss = 0.34, train accuracy = 93.75% Step 4550, train loss = 0.71, train accuracy = 68.75% Step 4600, train loss = 0.48, train accuracy = 87.50% Step 4650, train loss = 0.49, train accuracy = 68.75% Step 4700, train loss = 0.49, train accuracy = 68.75% Step 4750, train loss = 0.31, train accuracy = 81.25% Step 4800, train loss = 0.39, train accuracy = 75.00% Step 4850, train loss = 0.43, train accuracy = 87.50% Step 4900, train loss = 0.39, train accuracy = 81.25% Step 4950, train loss = 0.38, train accuracy = 93.75%
     
     
    Reading checkpoints...
    INFO:tensorflow:Restoring parameters from ./model/model.ckpt-4999
    Loading success, global_step is 4999
    This is a dog with possibility 0.580188


    再次验证发现此次判断较为准确
     
    Reading checkpoints...
    INFO:tensorflow:Restoring parameters from ./model/model.ckpt-4999
    Loading success, global_step is 4999
    This is a cat with possibility 0.967266



    参考文章 https://blog.csdn.net/qq_27318693/article/details/88843416
  • 相关阅读:
    Hibernate>查询缓存 小强斋
    hibernate>抓取策略 小强斋
    Spring>环境 及 为什么使用spring 小强斋
    Hibernate>一级缓存 小强斋
    Spring>环境 及 为什么使用spring 小强斋
    【设计模式系列】OO设计原则之LSPLiskov替换原则
    【设计模式系列】OO设计原则之SRP单一职责原则
    【Android】选项卡使用
    【人生】不管你挣多少, 钱永远是问题
    【设计模式系列】序
  • 原文地址:https://www.cnblogs.com/wfswf/p/16127701.html
Copyright © 2020-2023  润新知