你的手泄露了你的性别与年龄(内附CNTK与PyTorch 代码)

Posted 集智学园

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了你的手泄露了你的性别与年龄(内附CNTK与PyTorch 代码)相关的知识,希望对你有一定的参考价值。


用手就能够知道性别与年龄,透过卷积神经网路就能做到这么神奇的效果。从数据到模型,手把手教你用cntk或pytorch做出来。


随着深度学习的技术发展,越来越多的深度学习应用也进入到我们的世界。其中最为普遍的就是人脸识别,包括微软也推出了cognitive face api,可以透过人脸来识别身份,甚至可以用他来判断性别与年龄。各位以为用人脸识别性别年龄就很厉害了吗?在这边要来让各位瞧瞧深度学习的黑科技可以黑到甚么程度。我们将会透过微软的深度学习框架cntk以及脸书的pytorch(以下解说部份张贴的皆为cntk代码,文章最后会一次贴出两个框架个代码)来实作根据手的照片来判断人的性别与年龄。(什么,你想问为何没有tensorflow,不好意思,我就是不喜欢tensorflow.....哈)


首先关于使用的数据来自与谷歌(好吧,谷歌的戏份到此为止),他提供了一组数据包含了11000张手部的照片,同时包含手的主人的性别年龄与种族。我们将会撷取里面的性别(类别)与年龄(连续)数据来作为输出变数,手部图片作为输入特征。下载图片的路径如下:


https://sites.google.com/view/11khands


你的手泄露了你的性别与年龄(内附CNTK与PyTorch 代码)


如果你嫌下载后还要自己整理图片麻烦,我也有做好的图片懒人包,我已经有将图片数据向量化(为了方便下载,我把图片大小缩到64*64,原始图档是1600*1200,如果你想要大张一点的图片可以用以下介绍的方法来调整),以及比对好标签档的结果,我将他以pickle档案格式储存。您可以使用以下语法解析pickle档。解析完的结果是一个清单,清单内的每个项目也是一个子清单,里面分别包含两个ndarray,第一个是形状3*64*64的向量代表图档(请注意,cntk与pytorch都是CHW格式:通道*高*宽),另一个则是形状为3的向量,里面三个数值分别为id(数值相同代表是同一个人的不同角度的手)、年龄(介于0~100)以及性别(0是女1是男)


懒人包下载位置:


https://1drv.ms/u/s!AsqOV38qroofiOWrBW79ytqmS0pvJDE

imagearr=[]

with open('Data/hands.pkl', 'rb') as fp:
   imagearr=pickle.load(fp)


你的手泄露了你的性别与年龄(内附CNTK与PyTorch 代码)


你的手泄露了你的性别与年龄(内附CNTK与PyTorch 代码)


CNTK与pytorch的图片格式要求是一样的(tensorflow跟全业界唱反调,顺序都是反的),向量维度的排列是CHW(通道*高*宽),颜色的排列顺序是BGR(蓝绿红),也就都是依照字母顺序排列。


关于图片与向量的转换方法如下:

def img2array(img: Image):

    arr = np.array(img).astype(np.float32)

    arr=arr.transpose(2, 0, 1) #转成CHW

    arr=arr[::-1] #颜色排序为BGR

    return np.ascontiguousarray(arr)



def array2img(arr: np.ndarray):

    sanitized_img =arr[::-1]#转成RGB

    sanitized_img = np.maximum(0, np.minimum(255, np.transpose(arr, (1, 2, 0))))#转成HWC

    img = Image.fromarray(sanitized_img.astype(np.uint8))

    return img


为了供给建模使用的资料读取器,而且因为我想要毕其功于一役,让两种框架都可以一次适用,所以我写了一个通用的读取器来供应每个minibatch所需要的数据,其中读取图片时,我将图片向量除以255,而且读取年龄时,我将数值除以100,都是为了确保数据可以界于0~1之间,以方便收敛。在这个范例中因为篇幅关系暂时不放数据增强(data augmentation)。利用以下函数,每次调用都可以回传图片以及所需要标签。此外,要注意的是打乱图片顺序这个步骤很重要,谷哥的数据是有按照性别排序的。


idx=0

idx1=0

idxs=np.arange(len(imagearr))

np.random.shuffle(idxs) #打乱顺序

train_idxs=idxs[:7500]#定义训练集索引

test_idxs=idxs[7500:]#定义测试集索引




def next_minibatch(minibatch_size,is_train=True):

    global idx,idx1,train_idxs,test_idxs

    features = []

    labels_age=[]

    labels_gender=[]

    while len(features) < minibatch_size:

        try:

            if is_train:

                img = imagearr[train_idxs[idx]][0]

                img = img / 255. #将图片除以255,让向量介于0~1

                features.append(img)

                labels_age.append(np.asarray([imagearr[train_idxs[idx]][1][1]/100]))

                labels_gender.append(np.eye(2)[int(imagearr[train_idxs[idx]][1][2])])

                #np.eye(选项数)[索引]是快速建立one_hot的方法

            else:

                img = imagearr[test_idxs[idx1]][0]

                img = img / 255.  # 将图片除以255,让向量介于0~1

                features.append(img)

                labels_age.append(np.asarray([imagearr[test_idxs[idx1]][1][1] / 100]))

                labels_gender.append(np.eye(2)[int(imagearr[test_idxs[idx1]][1][2])])

                # np.eye(选项数)[索引]是快速建立one_hot的方法

        except OSError as e:

            print(e)

        if is_train:

            idx+=1

            if idx>=len(train_idxs): #如果取图片序号超过

                np.random.shuffle(train_idxs)

                idx = 0

        else:

            idx1 += 1

            if idx1 >= len(test_idxs):  # 如果取图片序号超过

                np.random.shuffle(test_idxs)

                idx1 = 0

    return np.asarray(features),np.asarray(labels_age).astype(np.float32),np.asarray(labels_gender).astype(np.float32)

    #如果是pytorch,必须改为np.asarray(labels_gender).astype(np.int64)


在这里要示范的卷积神经网路骨干网路用的是我最近很推崇的一篇文章所介绍的新架构「DenseNet」,原始论文出处如下。


Densely Connected Convolutional Networks

Gao Huang, Zhuang Liu, Laurens van der Maaten, Kilian Q. Weinberger

https://arxiv.org/abs/1608.06993


传统的卷积网路都是线性的,但当层数越多时,就有可能发生梯度弥散的问题,造成模型无法收敛,所以微软亚洲院2015年发展出的ResNet(残差神经网路)就使用了跳转连接(skip connection),来有效的将梯度挹注到后面神经层,这样模型就可以做出超深的架构,也不用担心难以收敛,微软2015年就以152层的ResNet获得了当年的imageNet冠军。但是深度学习在训练的过程中,当卷积神经网路将梯度传送到后层的时候,都会发生特征被随机遗失,这样这个特征就在也传不下去而无用了。为了解决这个问题,DenseNet的基本概念就是,每一层的特征都会传送到后面的「每」一层,这样就可以有效的确保信号不被丢失。


你的手泄露了你的性别与年龄(内附CNTK与PyTorch 代码)


DenseNet的基本结构称之为「稠密单元(Dense Block)」,他有几个重要的超参数:


k: 稠密单元层数

n_channel_start:初始通道数

glowth_rate:通道成长数


我们以下图为例,假设下图是一个k=4(向下传递4次,所以共5层),初始通道数32,成长数为16的Dense Block,我们分别计算每一层的输入通道数(从前面传进来的):


绿色:32+16(来自于红色)=48

紫色:48+16(来自于绿色)=64

黄色:64+16(来自于红色)+16(来自于绿色)=96

褐色:96+16(来自于红色)+16(来自于绿色)+16(来自于紫色)=144



你的手泄露了你的性别与年龄(内附CNTK与PyTorch 代码)



growth rate有就是每次会传递多少通道到后面的层数,以上面说明案例固定数值为16,但该卷积层的的通道数比这数字更大,因此等于是强迫每个卷积层要做一次特征选取,将特征精选之后传至后方,这种「Save the best for last (让人想起这首凡妮莎维廉斯的老歌)」,可以高度保全有效特征,以强化模型的收敛。DenseNet就是利用多个DenseBlock构成的网路。


你的手泄露了你的性别与年龄(内附CNTK与PyTorch 代码)


cntk与pytorch都没有预设的DenseNet,所以我用自订网路的方式实作了两个框架下的DenseNet

#需要使用cntk 2.4(含)以上版本,才支持cpu下执行batch normalization

#基本单元

def conv_bn(filter_size, num_filters, strides=(1, 1), init=he_normal(), activation=None, pad=True, bias=False,drop_rate=0.0,name=''):

    def apply_x(x):

        c = Convolution(filter_size, num_filters, activation=None, init=init, pad=pad, strides=strides,bias=bias,name=name)(x)

        r = BatchNormalization(map_rank=1, normalization_time_constant=4096, use_cntk_engine=False)(c)

        if drop_rate>0:

            r = dropout(r, drop_rate)

        if activation==None:

            return r

        else:

            return activation(r)

    return apply_x



def dense_block(x, depth, growth_rate, drop_rate=0.0, activation=leaky_relu):

    for i in range(depth):

        b = conv_bn((3, 3), growth_rate, init=he_uniform(), drop_rate=drop_rate, activation=activation)(x)

        x = splice(x, b, axis=0)

    return  x


#每次transition down图片长宽缩小1/2,因为strides=2

def transition_down(x,activation=leaky_relu, drop_rate=0):

    n_filters=x.shape[0]

    x =  conv_bn((3, 3), n_filters*2,strides=2,pad=True, init=he_uniform(), drop_rate=drop_rate, activation=activation)(x)

    x = conv_bn((1, 1), n_filters, (1, 1), pad=True, drop_rate=drop_rate, activation=activation)(x)

    return x



def down_path(x, depths, growth_rate, drop_rate=0, activation=leaky_relu):

    skips = []

    n = len(depths)

    for i in range(n):

        x = dense_block(x, depths[i], growth_rate, drop_rate=drop_rate, activation=activation)

        skips.append(x)

        if i<n-1:

            n_filters=depths[i]*growth_rate

            x = transition_down(x, activation=leaky_relu)

    return skips, x



由于图片只有64*64,经不起太多次图片缩小,因此我使用了5层k=4的Dense Block(我测试过3层,收敛快,但是结果测试集落差很大,显著过拟合)。由于我想要同时预测性别与年龄,cntk一个很神奇的特性就是可以在一个主要骨架下,同时接两个输出,只需要使用combine()函数,就可以将两个输出合并,未来只要做一次预测,就能产出两个预测结果,而且训练时也只要训练一次,而且骨干部分特征选取流程不需要做两次,是不是很方便呢。如果你是使用其他框架就只好做两个模型了。预测性别部分,使用的是长度为2的向量,最后一层全连接层活化函数使用softmax已进行分类。预测年龄部分,由于我们读取数据时已经将年龄除以100,因此年龄分布为0~1之间的常态分布,因此使用sigmoid函数效果较好。


def densenet(input,depths=(4, 4, 4, 4, 4), growth_rate=16,  n_channel_start=32,drop_rate=0.5, activation=leaky_relu):

    x = conv_bn((3, 3), n_channel_start, pad=True, strides=1, init=he_normal(0.02), activation=activation,bias=True)(input)

    skips, x = down_path(x, depths, growth_rate, drop_rate=drop_rate, activation=leaky_relu)

    x = conv_bn((1, 1), 64, (1, 1), pad=True, drop_rate=0, activation=activation)(x)


    z_gender = Dense(2, activation=softmax, init=he_normal(), init_bias=0)(x)  # 预测性别,使用softmax

    z_age=Dense(1,activation=sigmoid,init=he_normal(),init_bias=0)(x) #预测年龄,使用sigmoid,因为是预测连续数值,输出为0~1

    return combine([z_gender,z_age]) 


最后的训练过程可以透过以下函数来控制。首先宣告输入变数以及两个输出变数(性别与年龄),然后宣告模型、损失函数以及正确率指标。优化器使用的是adam,然后每50个minibatch就用测试集测试一次。


def train():

    minibatch_size=64

    learning_rate=0.001

    epochs=20


    # 定义输入与标签的变数

    input_var = input_variable((3, 64, 64))

    gender_var = input_variable(2)

    age_var = input_variable(1)


    # 宣告模型

    z = densenet(input_var)


    # 定义损失函数与错误率

    #性别损失函数使用cross_entropy_with_softmax,年龄则是使用均方根误差(mse)

    #错误率性别是用分类错误率,年龄是用均方根误差(mse)

    loss =cross_entropy_with_softmax(z[0], gender_var) +reduce_mean(squared_error(z[1], age_var))

    error = (classification_error(z[0], gender_var)+sqrt(reduce_mean(squared_error(z[1], age_var))))/2


    log_number_of_parameters(z);

    print()

    learner = C.adam(

        parameters=z.parameters,

        lr=C.learning_rate_schedule(learning_rate, C.UnitType.sample),

        momentum=C.momentum_schedule(0.95),

        gaussian_noise_injection_std_dev=0.001,

        l1_regularization_weight=10e-3, l2_regularization_weight=10e-4,

        gradient_clipping_threshold_per_sample=16)


    trainer = C.Trainer(z, (loss, error), learner)

    pp = C.logging.ProgressPrinter(10)


    trainstep = 2

    for epoch in range(epochs):

        training_step = 0

        while training_step <2000:

            raw_features, raw_age ,raw_gender= next_minibatch(minibatch_size)

            trainer.train_minibatch({input_var: raw_features, gender_var: raw_gender, age_var: raw_age})

            pp.update_with_trainer(trainer, True)


            if training_step % 100 == 0 :#and training_step > 0:

                test_features, test_ages, test_genders = next_minibatch(minibatch_size,is_train=False)

                pred = z(test_features)

                print('性别错误率:{0:.2%}%'.format(np.mean( np.not_equal(np.argmax(pred[0],-1), np.squeeze(np.argmax(test_genders,-1))).astype(np.float32))))

                avg_age = np.mean(test_ages)*100.

                avg_pred_age = np.mean(pred[1])*100.

                print('年龄均方根误差:{0:.2%}%'.format( np.sqrt(np.mean(np.square(pred[1] - np.squeeze(test_ages))))))

                print('实际平均年龄:{0:.2f}岁,平均预测年龄为{1:.2f}'.format(avg_age, avg_pred_age))

                z.save('Models\hands_combine.cnn')

            training_step += 1

        pp.epoch_summary(with_metric=True)

        z.save('Models\hands_combine.cnn')



我使用gtx-1080 (minibatch size=64)跑完第3个epoch的结果如下,年龄误差只有1.8%,性别目前仍有20.31%的错误率,看来手真的藏不住年龄啊。这个模型若是希望提升他的泛化效果,应该要在输入数据加入数据增强,后续的文章中我会们在来介绍数据增强的作法,以及介绍如何将深度学习模型工程化,与硬体连接变成可以落地的实际应用,请期待后续文章。  


你的手泄露了你的性别与年龄(内附CNTK与PyTorch 代码)


cntk完整语法

import PIL

from PIL import Image

import numpy as np

import random

import math

import datetime

import pickle

import cntk as C


from cntk.ops import *

from cntk.layers import *

from cntk.initializer import *

from cntk.logging import *

from cntk.train import *

from cntk.learners import *

from cntk.losses import *

from cntk.metrics import *

from cntk.device import *


# 是否使用GPU

is_gpu = True

if is_gpu:

    try_set_default_device(gpu(0))


imagearr=[]

with open('Data/hands.pkl', 'rb') as fp:

    imagearr=pickle.load(fp)

    print(len(imagearr))

    print(imagearr[0][0].shape)

    print(imagearr[0][1].shape)


def img2array(img: Image):

    arr = np.array(img).astype(np.float32)

    arr=arr.transpose(2, 0, 1) #转成CHW

    arr=arr[::-1] #颜色排序为BGR

    return np.ascontiguousarray(arr)


def array2img(arr: np.ndarray):

    sanitized_img =arr[::-1]#转成RGB

    sanitized_img = np.maximum(0, np.minimum(255, np.transpose(arr, (1, 2, 0))))#转成HWC

    img = Image.fromarray(sanitized_img.astype(np.uint8))

    return img


idx=0

idx1=0

idxs=np.arange(len(imagearr))

np.random.shuffle(idxs) #打乱顺序

train_idxs=idxs[:7500]#定义训练集索引

test_idxs=idxs[7500:]#定义测试集索引


def next_minibatch(minibatch_size,is_train=True):

    global idx,idx1,train_idxs,test_idxs

    features = []

    labels_age=[]

    labels_gender=[]

    while len(features) < minibatch_size:

        try:

            if is_train:

                img = imagearr[train_idxs[idx]][0]

                img = img / 255. #将图片除以255,让向量介于0~1

                features.append(img)

                labels_age.append(np.asarray([imagearr[train_idxs[idx]][1][1]/100]))

                labels_gender.append(np.eye(2)[int(imagearr[train_idxs[idx]][1][2])])

                #np.eye(选项数)[索引]是快速建立one_hot的方法

            else:

                img = imagearr[test_idxs[idx1]][0]

                img = img / 255.  # 将图片除以255,让向量介于0~1

                features.append(img)

                labels_age.append(np.asarray([imagearr[test_idxs[idx1]][1][1] / 100]))

                labels_gender.append(np.eye(2)[int(imagearr[test_idxs[idx1]][1][2])])

                # np.eye(选项数)[索引]是快速建立one_hot的方法

        except OSError as e:

            print(e)

        if is_train:

            idx+=1

            if idx>=len(train_idxs): #如果取图片序号超过

                np.random.shuffle(train_idxs)

                idx = 0

        else:

            idx1 += 1

            if idx1 >= len(test_idxs):  # 如果取图片序号超过

                np.random.shuffle(test_idxs)

                idx1 = 0

    return np.asarray(features),np.asarray(labels_age).astype(np.float32),np.asarray(labels_gender).astype(np.float32)

    #如果是pytorch,必须改为np.asarray(labels_gender).astype(np.int64)


#需要使用cntk 2.4(含)以上版本,才支持cpu下执行batch normalization

#基本单元

def conv_bn(filter_size, num_filters, strides=(1, 1), init=he_normal(), activation=None, pad=True, bias=False,drop_rate=0.0,name=''):

    def apply_x(x):

        c = Convolution(filter_size, num_filters, activation=None, init=init, pad=pad, strides=strides,bias=bias,name=name)(x)

        r = BatchNormalization(map_rank=1, normalization_time_constant=4096, use_cntk_engine=False)(c)

        if drop_rate>0:

            r = dropout(r, drop_rate)

        if activation==None:

            return r

        else:

            return activation(r)

    return apply_x


def dense_block(x, depth, growth_rate, drop_rate=0.0, activation=leaky_relu):

    for i in range(depth):

        b = conv_bn((3, 3), growth_rate, init=he_uniform(), drop_rate=drop_rate, activation=activation)(x)

        x = splice(x, b, axis=0)

    return  x


#每次transition down图片长宽缩小1/2,因为strides=2

def transition_down(x,activation=leaky_relu, drop_rate=0):

    n_filters=x.shape[0]

    x =  conv_bn((3, 3), n_filters*2,strides=2,pad=True, init=he_uniform(), drop_rate=drop_rate, activation=activation)(x)

    x = conv_bn((1, 1), n_filters, (1, 1), pad=True, drop_rate=drop_rate, activation=activation)(x)

    return x


def down_path(x, depths, growth_rate, drop_rate=0, activation=leaky_relu):

    skips = []

    n = len(depths)

    for i in range(n):

        x = dense_block(x, depths[i], growth_rate, drop_rate=drop_rate, activation=activation)

        skips.append(x)

        if i<n-1:

            n_filters=depths[i]*growth_rate

            x = transition_down(x, activation=leaky_relu)

    return skips, x


def densenet(input,depths=(4, 4, 4,4,4), growth_rate=16,  n_channel_start=32,drop_rate=0.5, activation=leaky_relu):

    x = conv_bn((3, 3), n_channel_start, pad=True, strides=1, init=he_normal(0.02), activation=activation,bias=True)(input)

    skips, x = down_path(x, depths, growth_rate, drop_rate=drop_rate, activation=leaky_relu)

    x = conv_bn((1, 1), 64, (1, 1), pad=True, drop_rate=0, activation=activation)(x)

    z_gender = Dense(2, activation=softmax, init=he_normal(), init_bias=0)(x)  # 预测性别,使用softmax

    z_age=Dense(1,activation=sigmoid,init=he_normal(),init_bias=0)(x) #预测年龄,使用sigmoid,因为是预测连续数值,输出为0~1

    return combine([z_gender,z_age])


def train():

    minibatch_size=64

    learning_rate=0.001

    epochs=20


    # 定义输入与标签的变数

    input_var = input_variable((3, 64, 64))

    gender_var = input_variable(2)

    age_var = input_variable(1)


    # 宣告模型

    z = densenet(input_var)


    # 定义损失函数与错误率

    #性别损失函数使用cross_entropy_with_softmax,年龄则是使用均方根误差(mse)

    #错误率性别是用分类错误率,年龄是用均方根误差(mse)

    loss =cross_entropy_with_softmax(z[0], gender_var) +reduce_mean(squared_error(z[1], age_var))

    error = (classification_error(z[0], gender_var)+sqrt(reduce_mean(squared_error(z[1], age_var))))/2


    log_number_of_parameters(z);

    print()

    learner = C.adam(

        parameters=z.parameters,

        lr=C.learning_rate_schedule(learning_rate, C.UnitType.sample),

        momentum=C.momentum_schedule(0.95),

        gaussian_noise_injection_std_dev=0.001,

        l1_regularization_weight=10e-3, l2_regularization_weight=10e-4,

        gradient_clipping_threshold_per_sample=16)


    trainer = C.Trainer(z, (loss, error), learner)

    pp = C.logging.ProgressPrinter(10)


    trainstep = 2

    for epoch in range(epochs):

        training_step = 0

        while training_step <2000:

            raw_features, raw_age ,raw_gender= next_minibatch(minibatch_size)

            trainer.train_minibatch({input_var: raw_features, gender_var: raw_gender, age_var: raw_age})

            pp.update_with_trainer(trainer, True)


            if training_step % 100 == 0 :#and training_step > 0:

                test_features, test_ages, test_genders = next_minibatch(minibatch_size,is_train=False)

                pred = z(test_features)

                print('性别错误率:{0:.2%}%'.format(np.mean( np.not_equal(np.argmax(pred[0],-1), np.squeeze(np.argmax(test_genders,-1))).astype(np.float32))))

                avg_age = np.mean(test_ages)*100.

                avg_pred_age = np.mean(pred[1])*100.

                print('年龄均方根误差:{0:.2%}%'.format( np.sqrt(np.mean(np.square(pred[1] - np.squeeze(test_ages))))))

                print('实际平均年龄:{0:.2f}岁,平均预测年龄为{1:.2f}'.format(avg_age, avg_pred_age))

                z.save('Models\hands_combine.cnn')

            training_step += 1

        pp.epoch_summary(with_metric=True)

        z.save('Models\hands_combine.cnn')


train()


  pytorch 完整语法

import PIL

from PIL import Image

import os

import time

import random

import math

import datetime

import pickle

import numpy as np

import torch

import torch.nn as nn

import torch.optim as optim

from torch.autograd import Variable

import torch.nn.functional as F

import torchvision

import torchvision.transforms as transforms


imagearr=[]

with open('Data/hands.pkl', 'rb') as fp:

    imagearr=pickle.load(fp)

    print(len(imagearr))

    print(imagearr[0][0].shape)

    print(imagearr[0][1].shape)


def img2array(img: Image):

    arr = np.array(img).astype(np.float32)

    arr=arr.transpose(2, 0, 1) #转成CHW

    arr=arr[::-1] #颜色排序为BGR

    return np.ascontiguousarray(arr)


def array2img(arr: np.ndarray):

    sanitized_img =arr[::-1]#转成RGB

    sanitized_img = np.maximum(0, np.minimum(255, np.transpose(arr, (1, 2, 0))))#转成HWC

    img = Image.fromarray(sanitized_img.astype(np.uint8))

    return img


idx=0

idx1=0

idxs=np.arange(len(imagearr))

np.random.shuffle(idxs) #打乱顺序

train_idxs=idxs[:9000]#定义训练集索引

test_idxs=idxs[9000:]#定义测试集索引


#pytorch数据读取器会很不一样

def next_minibatch(minibatch_size,is_train=True):

    global idx,idx1,train_idxs,test_idxs

    features = []

    labels_age=[]

    labels_gender=[]

    while len(features) < minibatch_size:

        try:

            if is_train:

                img = imagearr[train_idxs[idx]][0]

                img = img / 255. #将图片除以255,让向量介于0~1

                features.append(img)

                labels_age.append(np.asarray([imagearr[train_idxs[idx]][1][1]/100]))

                labels_gender.append(int(imagearr[train_idxs[idx]][1][2]))

                #pytorch的target必须为一维(长度=批次数),不可以是二维(n*1)

            else:

                img = imagearr[test_idxs[idx1]][0]

                img = img / 255.  # 将图片除以255,让向量介于0~1

                features.append(img)

                labels_age.append(np.asarray([imagearr[test_idxs[idx1]][1][1] / 100]))

                labels_gender.append(int(imagearr[test_idxs[idx]][1][2]))

                # pytorch的target必须为一维(长度=批次数),不可以是二维(n*1)

        except OSError as e:

            print(e)

        if is_train:

            idx+=1

            if idx>=len(train_idxs): #如果取图片序号超过

                np.random.shuffle(train_idxs)

                idx = 0

        else:

            idx1 += 1

            if idx1 >= len(test_idxs):  # 如果取图片序号超过

                np.random.shuffle(test_idxs)

                idx1 = 0

    return np.asarray(features),np.asarray(labels_age).astype(np.float32),np.asarray(labels_gender).astype(np.int64)


class DenseLayer(nn.Sequential):

    def __init__(self, in_channels, growth_rate):

        super().__init__()

        self.add_module('norm', nn.BatchNorm2d(in_channels))

        self.add_module('relu', nn.ReLU(True))

        self.add_module('conv', nn.Conv2d(in_channels, growth_rate, kernel_size=3, stride=1, padding=1, bias=True))

        self.add_module('drop', nn.Dropout2d(0.2))


    def forward(self, x):

        return super().forward(x)


class DenseBlock(nn.Module):

    def __init__(self, in_channels, growth_rate, n_layers, upsample=False):

        super().__init__()

        self.upsample = upsample

        self.layers = nn.ModuleList([DenseLayer(

            in_channels + i * growth_rate, growth_rate)

            for i in range(n_layers)])


    def forward(self, x):

        if self.upsample:

            new_features = []

            for layer in self.layers:

                out = layer(x)

                x = torch.cat([x, out], 1)

                new_features.append(out)

            return torch.cat(new_features, 1)

        else:

            for layer in self.layers:

                out = layer(x)

                x = torch.cat([x, out], 1)  # 1 = channel axis

            return x


class TransitionDown(nn.Sequential):

    def __init__(self, in_channels):

        super().__init__()

        self.add_module('norm', nn.BatchNorm2d(num_features=in_channels))

        self.add_module('relu', nn.ReLU(inplace=True))

        self.add_module('conv', nn.Conv2d(in_channels, in_channels, kernel_size=1, stride=1, padding=0, bias=True))

        self.add_module('drop', nn.Dropout2d(0.2))

        self.add_module('maxpool', nn.MaxPool2d(2))


    def forward(self, x):

        return super().forward(x)


class DenseNet(nn.Module):

    def __init__(self, in_channels=3, down_blocks=(4, 4, 4, 4, 4),

                 growth_rate=16, out_chans_first_conv=32):

        super().__init__()

        self.down_blocks = down_blocks

        cur_channels_count = 0

        skip_connection_channel_counts = []


        ## First Convolution ##

        self.add_module('firstconv', nn.Conv2d(in_channels=in_channels,

                                               out_channels=out_chans_first_conv, kernel_size=3,

                                               stride=1, padding=1, bias=True))

        cur_channels_count = out_chans_first_conv


        #####################

        # Downsampling path #

        #####################


        self.denseBlocksDown = nn.ModuleList([])

        self.transDownBlocks = nn.ModuleList([])

        for i in range(len(down_blocks)):

            self.denseBlocksDown.append(

                DenseBlock(cur_channels_count, growth_rate, down_blocks[i]))

            cur_channels_count += (growth_rate * down_blocks[i])

            skip_connection_channel_counts.insert(0, cur_channels_count)

            self.transDownBlocks.append(TransitionDown(cur_channels_count))


        # Softmax 预测性别使用##

        self.finalConv = nn.Conv2d(in_channels=cur_channels_count,

                                   out_channels=2, kernel_size=1, stride=1,

                                   padding=0, bias=True)

        self.fc = nn.Linear(8, 2)


    def forward(self, x):

        out = self.firstconv(x)


        skip_connections = []

        for i in range(len(self.down_blocks)):

            out = self.denseBlocksDown[i](out)

            skip_connections.append(out)

            out = self.transDownBlocks[i](out)

        out = self.finalConv(out)

        out = out.view(out.size(0), -1)

        out = self.fc(out)

        return out


def save_weights(model, epoch, loss, err):

    weights_fname = 'weights-%d-%.3f-%.3f.pth' % (epoch, loss, err)

    weights_fpath = "Models/{0}".format(weights_fname)

    torch.save({

        'startEpoch': epoch,

        'loss': loss,

        'error': err,

        'state_dict': model.state_dict()

    }, weights_fpath)


def load_weights(model, fpath):

    print("loading weights '{}'".format(fpath))

    weights = torch.load(fpath)

    startEpoch = weights['startEpoch']

    model.load_state_dict(weights['state_dict'])

    print("loaded weights (lastEpoch {}, loss {}, error {})"

          .format(startEpoch - 1, weights['loss'], weights['error']))

    return startEpoch


num_epochs=20

minibatch_size=16

learning_rate =1e-4 # 学习速率

model = DenseNet(in_channels=3, down_blocks=(4,4,4,4,4),growth_rate=16, out_chans_first_conv=32).cuda() #使用GPU


optimizer = torch.optim.RMSprop(model.parameters(), lr=learning_rate, weight_decay=1e-4)

criterion=nn.CrossEntropyLoss()



print('epoch start')

for epoch in range(num_epochs):

    mbs = 0

    trn_loss = 0

    trn_error = 0

    while mbs < 100:

        raw_features,  raw_age,  raw_gender  = next_minibatch(minibatch_size)

        #读取数据

        input, target = torch.from_numpy(raw_features), torch.from_numpy(raw_gender)#预测性别

        #将numpy转为variable

        input, target = Variable(input), Variable(target)

        #转换为cuda

        input, target = input.cuda(), target.cuda() #使用GPU

        output = model(input)

        loss = criterion(output, target)


        optimizer.zero_grad()#这句一定要放在loss.backward()之前

        loss.backward()

        optimizer.step()

        trn_loss += loss.data.item()

        pred_gender =output.cpu().max(1)[1].numpy()#.max(1)[1]相当于argmax

        actual_gender=target.cpu().numpy()


        err =np.sqrt(np.mean(np.not_equal(pred_gender, actual_gender)))

        trn_error += (1 - err)


        if mbs % 5 == 0 or mbs <= 5:

            print("Epoch: {}/{} ".format(epoch + 1, num_epochs),

                  "Step: {} ".format(mbs),

                  "Loss: {:.4f} ".format(loss.data.item()),

                  "accuracy: {:.3%}".format(err))

        mbs += 1

    trn_loss /= 100

    trn_error /= 100

    save_weights(model, epoch, trn_loss, trn_error)



本文作者


你的手泄露了你的性别与年龄(内附CNTK与PyTorch 代码)

尹相志

  1. 微软金牌讲师,在2006年-2017年连续获得微软最有价值专家称号(MVP)

  2. 曾任华院数据首席数据科学家,数据决策技术长

  3. 连续16年机器学习与 NLP 经验,领导多个领域的数据挖掘项目


今日课程直播


在本周三的集智AI学园直播课程《推估预测问题的建模与评测方法》中,具有 16 年数据处理经验的尹相志老师,会针对机器学习任务中最普遍的推估预测问题进行详细的讲解。


听课时间:7月11日(周三)19:30-21:30

直播内容:

  1. 推估问题:回归、神经网络、时间序列

  2. 推估问题案例:不动产价格预测、电力需求预测

  3. 如何处理与时间周期相关的数据

  4. 如何找出数值间的潜在关联

  5. 如何评估推估模型

  6. 实作:运用python演练推估问题



编辑:Queen

来源:

https://dotblogs.com.tw/allanyiin/2018/07/06/082817



获取更多更有趣的AI教程吧!

学园网站:campus.swarma.org

商务合作和投稿转载|swarma@swarma.org

以上是关于你的手泄露了你的性别与年龄(内附CNTK与PyTorch 代码)的主要内容,如果未能解决你的问题,请参考以下文章

基础类型

格式化输出

科幻的意义---《超新星纪元》后记

别让SSL证书暴露了你的网站服务器IP

前端简历怎么写

格式化输出