基于SVM的人脸图像性别分类

本来想用深度学习做的,但pytorch,tensorflow都死活装不上去,而且机器GPU也不行,所以就用传统SVM模型做了一个手写数字识别,又从网上下了数据,改成人脸性别识别。

数据:有20000张人脸图片,另有一个train.csv,共2列,分别为图片编号与性别(其中0为男,1为女)。

思路:
先读入数据,找到每张图片的性别,数据和性别分别存在两个数组里面,然后调用训练函数训练,训练后把模型用pickle保存起来以便下次使用。读入的数据也保存。
训练模型思路:先划分训练集和测试集,标准化数据,用SVM+poly核函数(经尝试比linear与rbf效果好)+随机搜索方法自动优化超参数(C,gamma和多项式系数)。
后来做成了命令行工具,代码如下
facereco.py

import train
TOTAL = 3000 # 要查找的文件数
IMG_HEIGHT = 50
IMG_SIZE = IMG_HEIGHT ** 2
dataX = np.zeros((TOTAL,IMG_SIZE),dtype="int32") # 数据
dataY = np.zeros((TOTAL,),dtype="uint8") # 标签,非0即1
retrain = False
def getHelp():
    return "使用方法:\n"\
            "python facereco.py [filename_list] [-h -H] [-retrain]\n"\
            "filename_list:要进行识别的文件名列表\n"\
            "-h -H 显示此帮助\n"\
            "-retrain:重新训练模型然后应用\n"\
            "示例:python facereco.py me.jpg\n python facereco.py me.jpg you.jpg -retrain"
def initData(path):
    # 读取数据
    # 思路:先读取train.csv获取编号和值,再读图片,把图片文件名中的编号提取出来,查找对应的值
    traind = np.loadtxt("train.csv",dtype="int32",delimiter=',')
    fileList = os.listdir(path)
    count = 0
    for f in fileList:
        if count >= TOTAL:
            break
        if os.path.isdir(f):
            continue # 不递归搜索
        pic_index = int(f.replace(".jpg", '')) # 提取图片编号
        #print(pic_index)
        index = np.where(traind[:,0] == pic_index) # 找到图片编号对应train.csv的第几行(注意只在第0列找)
        #np.where返回值是一个数组,大小是n(找到的元素个数)*m(维数,此处为1)
        dataY[count] = traind[index[0][0]][1]
        dataX[count,:] = img2Vector(os.path.join(path, f))
        count += 1
    with open("dataX.np","wb") as fp: #由于数据量较大,将数据存入本地下次直接读取
        pickle.dump(dataX, fp)
    with open("dataY.np","wb") as fp:
        pickle.dump(dataY,fp)

def img2Vector(img_path:str):
    # 一幅图片转为向量
    fp = open(img_path,"rb")
    img = Image.open(fp)
    img = img.resize((IMG_HEIGHT,IMG_HEIGHT)).convert("L") 
    imgData = np.array(img.getdata()).reshape(1,IMG_SIZE) # 转为行向量
    fp.close()
    return imgData
def parseArg():
    '''解析参数'''
    global retrain
    files = []
    argc = len(sys.argv)
    if argc == 1:
        print(getHelp())
        sys.exit(0)
    for arg in sys.argv[1:]:
        if arg.lower() == "-h":
            print(getHelp())
            sys.exit(0) # 存在-h/-H就输出帮助并退出
        if arg.lower() == "-retrain":
            retrain = True
        else:
            files.append(arg)
    return files
def trainModel():
    # 训练模型
    '''重新训练模型'''
    global dataX,dataY
    try:
        with open("dataX.np","rb") as fp: # 尝试加载数据
            dataX = pickle.load(fp)
        with open("dataY.np","rb") as fp:
            dataY = pickle.load(fp)
    except (OSError,pickle.UnpicklingError):
        initData("./train_data")
    svm,svmErr = train.SVM_train(dataX, dataY)
    print("测试集错误率%.3f"%svmErr)
    #print("用时%.3fs"%(time()-st))
    with open("model.pkl","wb") as fp:
        pickle.dump(svm,file=fp) #保存模型
    return svm
if __name__ == "__main__":
    files = parseArg()
    st = time()
    try:
        if retrain: # 如果参数中存在'retrain',则重新训练模型
            svm = trainModel()
        else: # 否则,尝试加载现有模型
            with open("model.pkl",mode="rb") as fp:
                svm = pickle.load(fp)
    except (OSError,pickle.UnpicklingError):
        print("未找到模型或模型损坏,将重新训练")
        svm = trainModel()
    # 开始用模型识别
    argc = len(files)
    for i in range(0,argc):
        dataX[i,:] = img2Vector(files[i])
    dataY = train.apply(svm, dataX[0:argc,:])
    for i in range(0,argc):
        gender = "男" if dataY[i] == 0 else "女"
        print("{:s} 识别结果为 {:s}".format(files[i],gender))
    print("用时%.3fs"%(time()-st))

train.py

try: # 尝试读加载scaler
    _fp = open("scaler.pkl","rb")
    _scaler = pickle.load(_fp)
    _fp.close()
except:
    _scaler = MinMaxScaler()

def SVM_train(dataX,dataY):
    '''
    Parameters
    ----------
    dataX : ndarray  X\n
    dataY : ndarray  Y\n

    Returns
    -------
    tuple 训练的模型与在测试集上的误差

    '''
    global _scaler
    paramList = { # 模型参数范围
        "C":np.random.uniform(0.6,6,size=(30,)),
        "gamma":np.random.uniform(0.001,0.05,size=(30,)),
        "degree":[3,4,5,6]
    }
    trainX,testX,trainY,testY = train_test_split(dataX,dataY,test_size=0.25)
    trainX = _scaler.fit(trainX).transform(trainX)
    testX = _scaler.transform(testX) # 注意在测试集上不能重新fit!
    svm = RandomizedSearchCV(SVC(kernel="poly"),param_distributions=paramList,n_iter=6,n_jobs=3)
    svm = svm.fit(trainX,trainY)
    # 在测试集上测试
    result = svm.predict(testX)
    fp = open("scaler.pkl","wb")
    pickle.dump(_scaler,fp) #保存scaler,不然retrain==False时scaler不可用
    fp.close()
    return svm,get_error(result, testY)
def get_error(result,Y_test):
    same = 0
    _len = len(result)
    print("Confusion martix on test set")
    cm = confusion_matrix(result, Y_test) # 计算混淆矩阵
    print(cm)
    for i in range(0,cm.shape[0]): # 计算正确个数,对角线上是正确的
        same += cm[i][i]
    return (_len - same) / _len
def apply(svm,data:np.ndarray): #应用模型
    '''
    应用svm模型\n
    Parameters
    ------------
    svm:svm模型\n
    data: ndarray  要应用的数据\n
    Returns
    -----------
    ndarray 结果
    '''
    global _scaler
    _data = _scaler.transform(data)
    return svm.predict(_data)

在测试集上的准确率是87.7%左右。单次训练需要35~40s。
用未被选中的训练数据检测,取男女各6人,发现男的里又有1人检测为女性,女的里有2人检测为男性,我自拍一张也测对了,这个概率应该勉强可以吧。

数据集参见这篇文章里的链接

你可能感兴趣的:(笔记,分类)