一.说明
本文参考《Python元学习 通用人工智能的实现》第二章部分内容,修改代码使其在通用环境下跑通。本文为实际项目的前期学习汇报,后续项目也许会出现在博客或者我的b站账户上(物理系的计算机选手)
原版完整代码:动手-元学习-使用-Python/2.4 使用暹罗网络的人脸识别-检查点.ipynb at master ·sudharsan13296/动手-使用 Python 进行元学习 ·GitHub
二.软件准备
1. python3.8以上版本,tensorflow2.x以上版本
2. 准备AT&T的人脸数据库
下载链接:AT&T面部数据库_图像数据_AT&T数据集-深度学习工具类资源-CSDN下载
三.创建输入对
孪生网络要求输入值成对并带有标签,所以必须以这种方式创建数据。
方法:我们从同一个文件夹中随机取出两张图片,并将其标记为正样本对;从两个文件中分别取出一张图像,并将它们标记为负样本对。具体如下图所示:
四.算法实现
1.导入库:
# 导入库
import re
import numpy as np
from PIL import Image
from sklearn.model_selection import train_test_split
from keras import backend as K
from keras.layers import Activation
from keras.layers import Input, Lambda, Dense, Dropout, Convolution2D, MaxPooling2D, Flatten
from keras.optimizers import rmsprop_v2
【注】如果下载的tf是以前的版本,最后一行调用的代码可能是RMSprop
2.定义一个函数来读取输入图像:
def read_image(filename, byteorder='>'):
# 首先将图像以RAW格式读入缓冲区
with open(filename, 'rb') as f:
buffer = f.read()
# 使用regax提取图片的头部,宽度,高度以及最大值
header, width, height, maxval = re.search(
b"(^P5\s(?:\s*#.*[\r\n])*"
b"(\d+)\s(?:\s*#.*[\r\n])*"
b"(\d+)\s(?:\s*#.*[\r\n])*"
b"(\d+)\s(?:\s*#.*[\r\n]\s)*)", buffer).groups()
# 然后,使用np.frombuffer,该函数用于将缓冲区转换为一维数组
return np.frombuffer(buffer,
dtype='u1' if int(maxval) < 256 else
byteorder + 'u2',
count=int(width)*int(height),
offset=len(header)
).reshape((int(height), int(width)))
【注】由于不同版本所带来语句可能细微变化,用下列代码测试所编写的函数是否可以成功运行,输出结果为一个图片加上一个数组(112,92)
Image.open("face_data/s1/1.pgm").show()
img = read_image('face_data/s1/1.pgm')
print(img.shape)
3.定义一个函数来生成数据:
def get_data(size, total_sample_size):
# 读取图像
image = read_image('face_data/s' + str(1) + '/' + str(1) + '.pgm', 'rw+')
# 缩减尺寸
image = image[::size, ::size]
# 获取新的尺寸
dim1 = image.shape[0]
dim2 = image.shape[1]
count = 0
# 初始化数组
x_geuine_pair = np.zeros([total_sample_size, 2, 1, dim1, dim2])
y_genuine = np.zeros([total_sample_size, 1])
for i in range(40):
for j in range(int(total_sample_size/40)):
ind1 = 0
ind2 = 0
# 从同一个目录中读取图像
while ind1 == ind2:
ind1 = np.random.randint(10)
ind2 = np.random.randint(10)
# 读取两个图像
img1 = read_image('face_data/s' + str(i+1) + '/' + str(ind1 + 1) + '.pgm', 'rw+')
img2 = read_image('face_data/s' + str(i+1) + '/' + str(ind2 + 1) + '.pgm', 'rw+')
# 缩减尺寸
img1 = img1[::size, ::size]
img2 = img2[::size, ::size]
# 将图片存入初始化的Numpy数组中
x_geuine_pair[count, 0, 0, :, :] = img1
x_geuine_pair[count, 1, 0, :, :] = img2
# 分配标签是1
y_genuine[count] = 1
count += 1
count = 0
x_imposite_pair = np.zeros([total_sample_size, 2, 1, dim1, dim2])
y_imposite = np.zeros([total_sample_size, 1])
for i in range(int(total_sample_size/10)):
for j in range(10):
while True:
ind1 = np.random.randint(40)
ind2 = np.random.randint(40)
if ind1 != ind2:
break
img1 = read_image('face_data/s' + str(ind1+1) + '/' + str(j + 1) + '.pgm', 'rw+')
img2 = read_image('face_data/s' + str(ind2+1) + '/' + str(j + 1) + '.pgm', 'rw+')
img1 = img1[::size, ::size]
img2 = img2[::size, ::size]
x_imposite_pair[count, 0, 0, :, :] = img1
x_imposite_pair[count, 1, 0, :, :] = img2
# 分配标签0
y_imposite[count] = 0
count += 1
X = np.concatenate([x_geuine_pair, x_imposite_pair], axis=0)/255
Y = np.concatenate([y_genuine, y_imposite], axis=0)
return X, Y
这里同样测试一下,测试代码如下(输出结果见备注):
X, Y = get_data(size, total_sample_size)
print(X.shape, Y.shape)
# # (20000, 2, 1, 56, 46) (20000, 1)
4.构建孪生网络:
# 构建孪生网络
def build_base_network(input_shape):
# 容器,在这个上面进行编译
seq = Sequential()
nb_filter = [6, 12]
kernel_size = 3
# 卷积层1
seq.add(Convolution2D(nb_filter[0], kernel_size, kernel_size, input_shape=input_shape,
border_mode='valid', dim_ordering='th'))
# seq.add(Conv2D(nb_filter, kernel_size=(3, 3), ))
# 激活函数
seq.add(Activation('relu'))
# 最大池化层
seq.add(MaxPooling2D(pool_size=(2, 2)))
seq.add(Dropout(.25))
# 卷积层2
seq.add(Convolution2D(nb_filter[1], kernel_size, kernel_size, border_mode='valid', dim_ordering='th'))
# 激活函数
seq.add(Activation('relu'))
# 最大池化层
seq.add(MaxPooling2D(pool_size=(2, 2), dim_ordering='th'))
seq.add(Dropout(.25))
# 扁平化层
seq.add(Flatten())
seq.add(Dense(128, activation='relu'))
seq.add(Dropout(0.1))
seq.add(Dense(50, activation='relu'))
return seq
【注】convolution这个单元在新版本的keras中是被替换成conv系列的,这里建议去官网学习一下详细的参数含义,我只能说要改的还是蛮多的。
我这里将做如下修改:
5.接下来把图像对输入基网络,它将返回嵌入,即特征向量:
input_dim = x_train.shape[2:]
img_a = Input(shape=input_dim)
img_b = Input(shape=input_dim)
base_network = build_base_network(input_dim)
feat_vecs_a = base_network(img_a)
feat_vecs_b = base_network(img_b)
这里要注意的是,feat_vecs指的是图像对的特征向量,接下来把这些特征向量输入能量函数,计算它们之间的距离,并使用欧氏距离作为能量函数:
# 能量函数
def euclidean_distance(vects):
x, y = vects
z = K.sqrt(K.sum(K.square(x-y), axis=1, keepdims=True))
return z
# 计算距离
def eucl_dist_output_shape(shapes):
shape1, shape2 = shapes
return (shape1[0],1)
distance = Lambda(euclidean_distance, output_shape=eucl_dist_output_shape([feat_vecs_a, feat_vecs_b]))
现在将轮数设置为13,并使用rmsprop进行优化,之后定义模型:
# 轮数
epochs = 3
# 优化
rms = rmsprop_v2
# 定义模型
model = Model(input=[img_a, img_b], output=distance)
6.定义损失函数:
# 定义损失函数
def contrastive_loss(y_true, y_pred):
margin = 1
z = K.mean(y_true * K.square(y_pred) + (1-y_true) * K.square(K.maximum(margin - y_pred, 0)))
return z
7.训练模型:
model.compile(loss=contrastive_loss, optimizer=rms)
# 训练模型
img_1 = x_train[:, 0]
img_2 = x_train[:, 1]
model.fit([img_1, img_2], y_train, validation_split=.25, batch_size=128, verbose=2, epochs=epochs)
# model.summary()