没练过这个项目,怎么做AI工程师?

640?wx_fmt=jpeg

从年初起,几家国际大厂的开发者大会,无论是微软Build、Facebook F8还是稍后的Google I/O,莫不把“AI优先”的大旗扯上云霄。

如果这一波AI大潮只是空喊几句口号,空提几个战略,空有几家炙手可热的创业公司,那当然成不了什么大气候。但风浪之下,我们看到的却是,Google一线的各大业务纷纷改用深度学习,落伍移动时代的微软则已拉起一支近万人的AI队伍。而国内一线大厂的情况,更是把AI牢牢把握住,试图再创高峰。

今天本文将分享一篇AI入门实战的项目经验分享,手把手带你进入AI的世界,让你消除对AI技术壁垒过高的恐惧~

【AI项目实战】多标签图像分类竞赛小试牛刀

初次拿到这个题目,想了想做过了猫狗大战这样的二分类,也做过cifar-10这样的多分类,类似本次比赛的题目多标签图像分类的确没有尝试过。6941个标签,每张图片可能没有标签也可能存在6941个标签,即各个标签之间是不存在互斥关系的,所以最终分类的损失函数不能用softmax而必须要用sigmoid。然后把分类层预测6941个神经元,每个神经元用sigmoid函数返回是否存在某个标签即可。


来蹚下整个流程看看,在jupyter notebook上做得比较乱,但是整个流程还是可以看出来的。深度学习模型用的Keras。


先导入train_csv数据,这里用的是最初版的训练csv文件,img_path里存在地址,后面做了处理。


扫描二维码关注公众号,回复: 4212150 查看本文章

code

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

from glob import glob
from tqdm import tqdm

import cv2
from PIL import Image

train_path = 'visual_china_train.csv'
train_df = pd.read_csv(train_path)
train_df.head()


640?wx_fmt=png

code
train_df.shape
#(35000, 2)


可以看到总共有35000张训练图片,第一列为图片名称(带地址,需处理),第二列为图片对应标签。

来看下是不是的确只有6941个标签:

code

tags = []
for i in range(train_df['tags'].shape[0]):
    for tag in train_df['tags'].iloc[i].split(','):
        tags.append(tag)

tags = set(tags)
len(tags)
#6941


事实证明标签总数无误,可以放心大胆地继续进行下去了。

然后我处理了下图片名称,并存到了img_paths列表里。

code

#如果使用的是官方后来更新的visual_china_train.csv,可以直接使用最后一行代码
for i in range(35000):
    train_df['img_path'].iloc[i] = train_df['img_path'].iloc[i].split('/')[-1]

img_paths = list(train_df['img_path'])


定义三个函数,其中:

  • hash_tag函数读入valid_tags.txt文件,并存入字典,形成索引和标签的对照。

  • load_ytrain函数读入tag_train.npz文件,并返回训练集的y_train,形式为ndarray,shape为(35000, 6941),即35000张图片和对应标签的one-hot编码。

  • arr2tag函数将预测结果的y_pred转变成对应的中文标签。(实际上最后还需要做下处理)

code

def hash_tag(filepath):
    fo = open(filepath, "r",encoding='utf-8')
    hash_tag = {}
    i = 0
    for line in fo.readlines():     #依次读取每行  
        line = line.strip()         #去掉每行头尾空白  
        hash_tag[i] = line
        i += 1
    return hash_tag

def load_ytrain(filepath):  
    y_train = np.load(filepath)
    y_train = y_train['tag_train']

    return y_train

def arr2tag(arr):
    tags = []
    for i in range(arr.shape[0]):
        tag = []
        index = np.where(arr[i] > 0.5)  
        index = index[0].tolist()
        tag =  [hash_tag[j] for j in index]

        tags.append(tag)
    return tags

读入valid_tags.txt,并生成索引和标签的映射。

code

filepath = "valid_tags.txt"
hash_tag = hash_tag(filepath)

hash_tag[1]
#'0到1个月'


载入y_train

code

y_train = load_ytrain('tag_train.npz')
y_train.shape
#(35000, 6941)


前期准备工作差不多做完了,开始导入训练集。此处有个坑,即原始训练集中存在CMYK格式的图片,传统图片处理一般为RGB格式,所以使用Image库中的convert函数对非RGB格式的图片进行转换。


code

nub_train = 5000  #可修改,前期尝试少量数据验证模型
X_train = np.zeros((nub_train,224,224,3),dtype=np.uint8)
i = 0

for img_path in img_paths[:nub_train]:
    img = Image.open('train/' + img_path)
    if img.mode != 'RGB':
        img = img.convert('RGB')
    img = img.resize((224,224))
    arr = np.asarray(img)
    X_train[i,:,:,:] = arr
    i += 1


训练集导入完成,来看图片的样子,判断下图片有没有读入错误之类的问题。

code

fig,axes = plt.subplots(6,6,figsize=(2020))

j = 0
for i,img in enumerate(X_train[:36]):
    axes[i//6,j%6].imshow(img)
    j+=1



640?wx_fmt=png

看样子还不错,go on! 训练集的X_train、y_train都拿到了,分割出验证集。这里要说明一下,官方的y_train里图片名称与X_train里图片名称是对应的所以可以直接分割。

code

from sklearn.model_selection import train_test_split
X_train2,X_val,y_train2,y_val = train_test_split(X_train, y_train[:nub_train], test_size=0.2, random_state=2018)


数据准备完成,开始搭建模型。咳咳,先从简单的入手哈,此模型仿tinymind上一次的汉字书法识别大赛中“真的学不会”大佬的结构来搭的,又加了些自己的东西,反正简单模型试试水嘛。

code

from keras.layers import *
from keras.models import *
from keras.optimizers import *
from keras.callbacks import *

def bn_prelu(x):
    x = BatchNormalization()(x)
    x = PReLU()(x)
    return x

def build_model(out_dims, input_shape=(2242243)):
    inputs_dim = Input(input_shape)
    x = Lambda(lambda x: x / 255.0)(inputs_dim) #在模型里进行归一化预处理

    x = Conv2D(16, (33), strides=(22), padding='same')(x)
    x = bn_prelu(x)
    x = Conv2D(16, (33), strides=(11), padding='same')(x)
    x = bn_prelu(x)
    x = MaxPool2D(pool_size=(22))(x)

    x = Conv2D(32, (33), strides=(11), padding='same')(x)
    x = bn_prelu(x)
    x = Conv2D(32, (33), strides=(11), padding='same')(x)
    x = bn_prelu(x)
    x = MaxPool2D(pool_size=(22))(x)

    x = Conv2D(64, (33), strides=(11), padding='same')(x)
    x = bn_prelu(x)
    x = MaxPool2D(pool_size=(22))(x)

    x = Conv2D(128, (33), strides=(11), padding='same')(x)
    x = bn_prelu(x)
    x = GlobalAveragePooling2D()(x)

    dp_1 = Dropout(0.5)(x)

    fc2 = Dense(out_dims)(dp_1)
    fc2 = Activation('sigmoid')(fc2) #此处注意,为sigmoid函数

    model = Model(inputs=inputs_dim, outputs=fc2)
    return model


看下模型结构:

code

model = build_model(6941)
model.summary()


_________________________________________________________________Layer (type)                 Output Shape              Param #   
=================================================================input_1 (InputLayer)         (None, 224, 224, 3)       0         
_________________________________________________________________lambda_1 (Lambda)            (None, 224, 224, 3)       0         
_________________________________________________________________conv2d_1 (Conv2D)            (None, 112, 112, 16)      448       
_________________________________________________________________batch_normalization_1 (Batch (None, 112, 112, 16)      64        
_________________________________________________________________p_re_lu_1 (PReLU)            (None, 112, 112, 16)      200704    
_________________________________________________________________conv2d_2 (Conv2D)            (None, 112, 112, 16)      2320      
_________________________________________________________________batch_normalization_2 (Batch (None, 112, 112, 16)      64        
_________________________________________________________________p_re_lu_2 (PReLU)            (None, 112, 112, 16)      200704    
_________________________________________________________________max_pooling2d_1 (MaxPooling2 (None, 56, 56, 16)        0         
_________________________________________________________________conv2d_3 (Conv2D)            (None, 56, 56, 32)        4640      
_________________________________________________________________batch_normalization_3 (Batch (None, 56, 56, 32)        128       
_________________________________________________________________p_re_lu_3 (PReLU)            (None, 56, 56, 32)        100352    
_________________________________________________________________conv2d_4 (Conv2D)            (None, 56, 56, 32)        9248      
_________________________________________________________________batch_normalization_4 (Batch (None, 56, 56, 32)        128       
_________________________________________________________________p_re_lu_4 (PReLU)            (None, 56, 56, 32)        100352    
_________________________________________________________________max_pooling2d_2 (MaxPooling2 (None, 28, 28, 32)        0         
_________________________________________________________________conv2d_5 (Conv2D)            (None, 28, 28, 64)        18496     
_________________________________________________________________batch_normalization_5 (Batch (None, 28, 28, 64)        256       
_________________________________________________________________p_re_lu_5 (PReLU)            (None, 28, 28, 64)        50176     
_________________________________________________________________max_pooling2d_3 (MaxPooling2 (None, 14, 14, 64)        0         
_________________________________________________________________conv2d_6 (Conv2D)            (None, 14, 14, 128)       73856     
_________________________________________________________________batch_normalization_6 (Batch (None, 14, 14, 128)       512       
_________________________________________________________________p_re_lu_6 (PReLU)            (None, 14, 14, 128)       25088     
_________________________________________________________________global_average_pooling2d_1 ( (None, 128)               0         
_________________________________________________________________dropout_1 (Dropout)          (None, 128)               0         
_________________________________________________________________dense_1 (Dense)              (None, 6941)              895389    
_________________________________________________________________activation_1 (Activation)    (None, 6941)              0         
=================================================================Total params: 1,682,925
Trainable params: 1,682,349
Non-trainable params: 576_________________________________________________________________

由于比赛要求里最终得分标准是fmeasure而不是acc,故网上找来一段代码用以监测训练中查准率、查全率、fmeasure的变化。原地址找不到了,故而无法贴上,罪过罪过。

code

import keras.backend as K

def precision(y_true, y_pred):
    # Calculates the precision
    true_positives = K.sum(K.round(K.clip(y_true * y_pred, 01)))
    predicted_positives = K.sum(K.round(K.clip(y_pred, 01)))
    precision = true_positives / (predicted_positives + K.epsilon())
    return precision

def recall(y_true, y_pred):
    # Calculates the recall
    true_positives = K.sum(K.round(K.clip(y_true * y_pred, 01)))
    possible_positives = K.sum(K.round(K.clip(y_true, 01)))
    recall = true_positives / (possible_positives + K.epsilon())
    return recall

def fbeta_score(y_true, y_pred, beta=1):
    # Calculates the F score, the weighted harmonic mean of precision and recall.
    if beta < 0:
        raise ValueError('The lowest choosable beta is zero (only precision).')

    # If there are no true positives, fix the F score at 0 like sklearn.
    if K.sum(K.round(K.clip(y_true, 01))) == 0:
        return 0

    p = precision(y_true, y_pred)
    r = recall(y_true, y_pred)
    bb = beta ** 2
    fbeta_score = (1 + bb) * (p * r) / (bb * p + r + K.epsilon())
    return fbeta_score

def fmeasure(y_true, y_pred):
    # Calculates the f-measure, the harmonic mean of precision and recall.
    return fbeta_score(y_true, y_pred, beta=1)


这里稍做图片增强,用Keras里的ImageDataGenerator函数,同时还可生成器方法进行训练。

code

from keras.preprocessing.image import ImageDataGenerator

train_datagen = ImageDataGenerator(width_shift_range = 0.1
                                 height_shift_range = 0.1
                                 zoom_range = 0.1)
val_datagen = ImageDataGenerator()     #验证集不做图片增强

batch_size = 8

train_generator = train_datagen.flow(X_train2,y_train2,batch_size=batch_size,shuffle=False
val_generator = val_datagen.flow(X_val,y_val,batch_size=batch_size,shuffle=False)


开始训练。这里在ModelCheckpoint里设置monitor监控feasure,mode为max,不再以最低loss作为模型最优的判断标准(个人做法,好坏可自行实验判断)。

code

checkpointer = ModelCheckpoint(filepath='weights_best_simple_model.hdf5'
                            monitor='val_fmeasure',verbose=1, save_best_only=True, mode='max')
reduce = ReduceLROnPlateau(monitor='val_fmeasure',factor=0.5,patience=2,verbose=1,min_delta=1e-4,mode='max')

model.compile(optimizer = 'adam',
           loss='binary_crossentropy',
           metrics=['accuracy',fmeasure,recall,precision])

epochs = 20

history = model.fit_generator(train_generator,
       validation_data = val_generator,
       epochs=epochs,
       callbacks=[checkpointer,reduce],
       verbose=1)


训练了20个epoch,这里给出第20个epoch时的训练结果,可以看到,val_loss 0.0233,其实已经挺低了;val_acc0.9945,参考意义不大(暂时不清楚有什么参考意义~~);val_fmeasure0.17,嗯。。任重道远啊。


训练了20个epoch,这里给出第20个epoch时的训练结果,可以看到,val_loss 0.0233,其实已经挺低了;val_acc0.9945,参考意义不大(暂时不清楚有什么参考意义~~);val_fmeasure0.17,嗯。。任重道远啊。

Epoch 20/20500/500 [==============================] - 48s 96ms/step - loss: 0.0233 - acc: 0.9946 - fmeasure: 0.1699 - recall: 0.0970 - precision: 0.7108 - val_loss: 0.0233 - val_acc: 0.9946 - val_fmeasure: 0.1700 - val_recall: 0.0968 - val_precision: 0.7162
    Epoch 00020: val_fmeasure did not improve from 0.17148

以上只给出5000张图片的简单模型训练方法,但数据处理,搭建模型以及训练过程已经很清晰明了了,后面的进阶之路就凭大家各显身手了。


然后开始进行预测,导入测试集(当然是在训练集全部训练之后再进行测试集的预测)。

code

nub_test = len(glob('valid/*'))
X_test = np.zeros((nub_test,224,224,3),dtype=np.uint8)
path = []
i = 0
for img_path in tqdm(glob('valid/*')):
    img = Image.open(img_path)
    if img.mode != 'RGB':
        img = img.convert('RGB')
    img = img.resize((224,224))
    arr = np.asarray(img)
    X_test[i,:,:,:] = arr
    i += 1

100%|██████████████████████████████████████████████████████████████████████████████| 8000/8000 [02:18&lt;00:0057.91it/s]


预测测试集并利用arr2tag函数将结果转为中文标签,以便生成提交文件。

code

y_pred = model.predict(X_test)
y_tags = arr2tag(y_pred)


生成提交文件:

code

import os
img_name = os.listdir('valid/')
img_name[:10]



['000effcf2091ae3895074838b7e5f571186ab362.jpg',
 '0014455e5fbfd0961039fe23675debbb1a7b2308.jpg',
 '002138959ee7a14eb2860100392a384f8a85425f.jpg',
 '002414411ce17c6c7ab5d36dd3f956d0691ba495.jpg',
 '002780359fda7f09e6d1fc52d88aff90c6e8298b.jpg',
 '002ad24891ddf815bb86e4eca34415b1b44c9e4b.jpg',
 '002c284f94299bcee51733f7d6b17f3e4792d8c5.jpg',
 '002cf4b15887f32b688113a2a7a3f5786896d019.jpg',
 '003d4c12160b90fbbb2bd034ee30c251a45d9037.jpg',
 '0043ab4460cc79bfbea3db69d2a55d5f35600a37.jpg']

arr2tag函数得到的每张图片的标签是list格式,需转成str,在这里操作。经实验,windows中的方法与ubuntu中不同,后面也给出了ubuntu中本步的处理方法。

code

# windows
import pandas as pd

df = pd.DataFrame({'img_path':img_name, 'tags':y_tags})
for i in range(df['tags'].shape[0]):
    df['tags'].iloc[i] = ','.join(str(e) for e in  df['tags'].iloc[i])
df.to_csv('submit.csv',index=None)

df.head()


640?wx_fmt=png

code

# #Ubuntu
import pandas as pd

df = pd.DataFrame({'img_path':img_name, 'tags':y_tags})
for i in range(df['tags'].shape[0]):
    df['tags'].iloc[i] = df['tags'].iloc[i][2:-2].replace('\'',"").replace('\'',"")
df.to_csv('submit.csv',index=None)


整篇到此结束,有几点要说的:

  1. 提高方法。不用说,肯定是上预训练模型,可能再进行模型融合效果会更好。官方大大说整个标签由于人工标注,可能会跟机器预测出来的有别差,毕竟看预测结果中出现的 “一个人,人,仅一个女人,仅一个青年女人,仅女人,仅成年人” ,如果由人类来标注可能不会这么啰嗦~~所以可以考虑NLP方法对标签进行一些处理(我不会)。另外网上查到了个诡异的做法,说可以把fmeasure变成损失函数去训练模型(fmeasure不可导),我想如果有办法做到应该效果不错吧。

  2. 不足之处。训练过程中监控fmeasure和监控loss的做法,看上去应该是fmeasure没错,不过自己对于这块研究不够,只能凭感觉在做,各位看官可自由发挥。

  3. 整篇文章代码只有查准率、查全率、fmeasure部分为网上摘取,其他均为原创代码(略有借鉴),其实是想说,代码可能有些地方稚嫩,还望各位大佬们海涵。


如果想深入学习的话,我推荐还是报名实训营,让更有经验的大咖导师为你指路,效率和效果都会翻倍!


在这里推荐 CSDN 学院出品的《人工智能工程师直通车》实训营,目的是:通过 120 天的实战,将学员培养达到具备一年项目经验的人工智能工程师水平。CSDN 百天计划课程共分为 3 个阶段,4 个月完成。


联系 CSDN 学院职场规划师,

获取一对一专属服务

(包括:IT 职场规划服务/专属折扣)

640?wx_fmt=jpeg

为什么报名CSDN学院?

很多学员都曾苦恼,工作中缺乏“好师傅”,很多Bug,也都得绞尽脑汁自己解决。在全栈特训营,这些问题都将不存在。

我们采取讲师+课程助教的服务模式。

授课的两位老师分别是:中国科学院大学计算机与控制学院副教授卿来云老师、和计算机视觉领域处理大牛智亮老师。两位老师一个偏重理论知识、算法推倒;一个偏重实际操作,实战经验,可以全方面的为学员提供知识的讲解。 

课程助教将会带领你一起攻克项目,Review你的代码并给出意见。最后,课程助教会带你们一起进行项目最后上线路演,并接受导师的点评。

640?wx_fmt=other

(课程大纲)

对于课业完成优秀的同学,CSDN 学院还将提供名企推荐,看到这里,你想不想踩在过来人的肩膀上,轻松实现转型人工智能工程师呢?

不妨添加 CSDN 学院小姐姐的微信聊一聊吧,报程序人生的粉丝,还将获得千元优惠券哦。

联系 CSDN 学院职场规划师,

获取一对一专属服务

(包括:IT 职场规划服务/专属折扣)

640?wx_fmt=jpeg

最后,祝愿所有的朋友能学有所成~

猜你喜欢

转载自blog.csdn.net/dQCFKyQDXYm3F8rB0/article/details/84351408