本来想用深度学习做的,但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人检测为男性,我自拍一张也测对了,这个概率应该勉强可以吧。
数据集参见这篇文章里的链接