97人参与 • 2024-08-05 • 神经网络
目录
用keras工具包搭建训练自己的一个传统神经网络,用来识别猫/狗/羊三种图片。
数据集:
# 导入所需要的工具包
# 将建模的结果画出来
import matplotlib
# 做标签,数据切分,展示结果的对比
from sklearn.preprocessing import labelbinarizer
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
# keras工具包的操作
from keras.models import sequential
from keras.layers import dropout
# from keras.layers.core import dense
from tensorflow.python.keras.layers.core import dense
from keras.optimizers import sgd
from keras import initializers
from keras import regularizers
# 图像路径处理操作
from my_utlis import utlis_paths
# 一些基本库
import matplotlib.pyplot as plt
import numpy as np
import argparse
import random
import pickle
import cv2
import os
os.environ["cuda_visible_devices"] = "0"
(1).如果只安装了keras工具包没安装cv2库,可以参考以下链接在anaconda的虚拟环境中安装cv2库。
(2).如果 from keras.layers.core import dense 显示此结果
可以将导入代码改为如下方式
from tensorflow.python.keras.layers.core import dense
(3).在代码开头加入os.environ["cuda_visible_devices"] = "0"这行代码可以用gpu训练网络,“0”为可以使用的gpu编号,可以在cmd中输入nvidia -smi查询可用的gpu编号:
# --dataset --model --label-bin --plot
# 输入参数
ap = argparse.argumentparser()
ap.add_argument("-d", "--dataset", required=true,
help="path to input dataset of images")
ap.add_argument("-m", "--model", required=true,
help="path to output trained model")
ap.add_argument("-l", "--label-bin", required=true,
help="path to output label binarizer")
ap.add_argument("-p", "--plot", required=true,
help="path to output accuracy/loss plot")
args = vars(ap.parse_args())
设置参数
右键项目点击修改运行配置
设置参数的值
设置好参数后在对应处创建文件夹
# 获取图像数据路径,方便后续读取
imagepaths = sorted(list(utlis_paths.list_images('./dataset')))
random.seed(42)
random.shuffle(imagepaths)
# 遍历读取数据
for imagepath in imagepaths:
# 读取图像数据,后续步骤中使用了神经网络,需要将图像数据设置成一维
image = cv2.imread(imagepath) # cv2.imread方法读取数据
image = cv2.resize(image, (32, 32)).flatten() # 将读取的图片数据设置成相同的大小
data.append(image)
# 读取标签
label = imagepath.split(os.path.sep)[-2]
labels.append(label)
# scale图像数据
data = np.array(data, dtype="float") / 255.0
labels = np.array(labels)
调用utlis_paths.list_images,获取路径的文件
通过from my_utlis import utlis_paths引入utlis_paths,所以需要在my_utlis文件夹中生成一个utlis_paths文件并在其中完成相应的函数定义:
import os
image_types = (".jpg", ".jpeg", ".png", ".bmp", ".tif", ".tiff")
def list_images(basepath, contains=none):
# 返回有效的图片路径数据集
return list_files(basepath, validexts=image_types, contains=contains)
def list_files(basepath, validexts=none, contains=none):
# 遍历图片数据目录,生成每张图片的路径
for (rootdir, dirnames, filenames) in os.walk(basepath):
# 循环遍历当前目录中的文件名
for filename in filenames:
# if the contains string is not none and the filename does not contain
# the supplied string, then ignore the file
if contains is not none and filename.find(contains) == -1:
continue
# 通过确定.的位置,从而确定当前文件的文件扩展名
ext = filename[filename.rfind("."):].lower()
# 检查文件是否为图像,是否应进行处理
if validexts is none or ext.endswith(validexts):
# 构造图像路径
imagepath = os.path.join(rootdir, filename)
yield imagepath
指定一个随机种子,确保每次的切割相同,同时将数据洗牌,打乱数据
random.seed(42) # 指定随机种子
random.shuffle(imagepaths) # 把数据重新洗牌,即将数据打乱
读取数据,并将所有的数据设置为统一规格
image = cv2.imread(imagepath) # cv2.imread方法读取数据
image = cv2.resize(image, (32, 32)).flatten() # 将读取的图片数据设置成相同的大小
data.append(image) # append()函数向列表末尾添加一个元素
flatten()函数将图片参数拉长。
eg.32x32x3大小的图片,拉长为3072个像素点,即变为1x3072的矩阵
给读取出来的数据设置标签(读取数据和设置标签一般放在一起)
label = imagepath.split(os.path.sep)[-2]
labels.append(label)
scale归一化获得的图片数据,使得特征值在0~1之间浮动
# scale图像数据
data = np.array(data, dtype="float") / 255.0
# 数据集切分
(trainx, testx, trainy, testy) = train_test_split(data,
labels, test_size=0.25, random_state=42)
# 转换标签,one-hot格式
lb = labelbinarizer()
trainy = lb.fit_transform(trainy)
testy = lb.transform(testy)
划分训练集和测试集:
(trainx, testx, trainy, testy) = train_test_split(data,
labels, test_size=0.25, random_state=42)
函数原型:
x_train, x_test, y_train, y_test = train_test_split(train_data, train_target, test_size, random_state, shuffle)
变量:
x_train:划分的训练集数据
x_test:划分的测试集数据
y_train:划分的训练集标签
y_test:划分的测试集标签
参数:
train_data:还未划分的数据集
train_target:还未划分的标签
test_size:分割比例,默认为0.25,即测试集占完整数据集的比例
random_state:随机数种子,应用于分割前对数据的洗牌。可以是int,randomstate实例或none,默认值=none。设成定值意味着,对于同一个数据集,只有第一次运行是随机的,随后多次分割只要rondom_state相同,则划分结果也相同。
shuffle:是否在分割前对完整数据进行洗牌(打乱),默认为true,打乱
转换标签:
lb = labelbinarizer()
trainy = lb.fit_transform(trainy)
testy = lb.transform(testy)
最后得到的结果是对应三个动物种类狗、猫、羊的概率值,所以就应当有三个标签,需要对当前的标签进行转换,转换为满足要求的one-hot标签格式。
# 网络模型结构 3072-512-256-3
model = sequential()
# kernel_regularizer=regularizers.12(0.01)
# keras.initializers.truncatednormal(mean=0.0, stddex=0.05, seed=none)
# initializers.random_normal
# #model.add(dropout(0.8))
model.add(keras.input(shape=(3072,)))
model.add(dense(512, activation="relu"))
model.add(dense(256, activation="relu"))
model.add(dense(len(lb.classes_), activation="softmax",))
# 初始化参数
init_lr = 0.01
epochs = 200
# 给定损失函数和评估方法
print("[info] 准备训练网络...")
opt = sgd(lr=init_lr)
model.compile(loss="categorical_crossentropy", optimizer=opt,
metrics=["accuracy"])
# 训练网络模型
h = model.fit(trainx, trainy, validation_data=(testx,testy),
epochs=epochs, batch_size=32)
# 测试网络模型
print("[info] 正在评估模型")
predictions = model.predict(testx, batch_size=32)
print(classification_report(testy.argmax(axis=1),
predictions.argmax(axis=1), target_names=lb.classes_))
该网络模型的共有三层,初始特征值有3072个,由3072-512-256-3,最后得到对应三个图像类别的概率值。
设置模型的类型为sequential :
model = sequential()
对于sequential模型的相关信息,可以查询下述keras的官方网站:
https://keras.io/guides/sequential_model/https://keras.io/guides/sequential_model/
向神经网络中添加三层:
第一层:
model.add(keras.input(shape=(3072,)))
model.add(dense(512, activation="relu"))
首先第一层一般要通过input(n,)函数告诉网络起始层有多少个神经元,然后512表示的是转换为下一层的神经元为512个,激活函数设置的是relu。
第二层:
model.add(dense(256, activation="relu"))
经过这一层得到的下一层的神经元应当有256个,同时激活函数设置的为relu。
第三层:
model.add(dense(len(lb.classes_), activation="softmax",))
经过这一层得到下一层的神经元个数为len(lb.classes_)的返回值,此处为3,同时激活函数为softmax,最后得到对应的不同类别的概率值。
初始化参数:
init_lr = 0.01
epochs = 200
init_lr为学习率,epochs为遍历整个数据集的次数
设置损失函数和评估方法:
opt = sgd(lr=init_lr)
model.compile(loss="categorical_crossentropy", optimizer=opt,
metrics=["accuracy"])
sgd为随机梯度下降,将opt优化器设置为随机梯度下降,同时将学习率告诉sgd。
选择的损失函数为categorical_crossentropy,即为交叉熵损失函数 。
最后再获取accuracy准确率
训练网络模型:
h = model.fit(trainx, trainy, validation_data=(testx,testy),
epochs=epochs, batch_size=32)
测试网络模型:
print("[info] 正在评估模型")
predictions = model.predict(testx, batch_size=32)
print(classification_report(testy.argmax(axis=1),
predictions.argmax(axis=1), target_names=lb.classes_)))
# 调试完成后,绘制结果曲线
n = np.arange(0, epochs)
plt.style.use("ggplot")
plt.figure()
plt.plot(n, h.history["loss"], label="train_loss")
plt.plot(n, h.history["val_loss"], label="val_loss")
plt.plot(n, h.history["acc"], label="train_acc")
plt.plot(n, h.history["val_acc"], label="val_acc")
plt.title("training loss and accuracy (simple nn)")
plt.xlabel("epoch #")
plt.ylabel("loss/accuracy")
plt.legend()
plt.savefig('./output/simple_nn_plot.png') # 参数为保存的路径
# 保存模型到本地
print("[info] 正在保存模型")
model.save(args["model"])
f = open('./output/simple_nn_lb.pickle', "wb") # 保存标签数据
f.write(pickle.dumps(lb))
f.close()
对于acc参数,不同版本的keras对应的写法不同,如果引入acc和val_acc报错,可以尝试改为accuracy和val_accuracy,反之亦然。
项目结构:
nn_train.py:
# 导入所需要的工具包
# 将建模的结果画出来
import keras
import matplotlib
# 做标签,数据切分,展示结果的对比
from sklearn.preprocessing import labelbinarizer
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
# keras工具包的操作
from keras.models import sequential
from keras.layers import dropout
# from keras.layers.core import dense
from tensorflow.python.keras.layers.core import dense
from keras.optimizers import sgd
from keras import initializers
from keras import regularizers
# 图像路径处理操作
from my_utlis import utlis_paths
# 一些基本库
import matplotlib.pyplot as plt
import numpy as np
import argparse
import random
import pickle
import cv2
import os
# --dataset --model --label-bin --plot
# 输入参数
ap = argparse.argumentparser()
ap.add_argument("-d", "--dataset", required=true,
help="path to input dataset of images")
ap.add_argument("-m", "--model", required=true,
help="path to output trained model")
ap.add_argument("-l", "--label-bin", required=true,
help="path to output label binarizer")
ap.add_argument("-p", "--plot", required=true,
help="path to output accuracy/loss plot")
args = vars(ap.parse_args())
print("[info] 开始读取数据")
data = []
labels = []
# 获取图像数据路径,方便后续读取
imagepaths = sorted(list(utlis_paths.list_images('./dataset')))
random.seed(42) # 指定随机种子
random.shuffle(imagepaths) # 把数据重新洗牌,即将数据打乱
# 遍历读取数据
for imagepath in imagepaths:
# 读取图像数据,后续步骤中使用了神经网络,需要将图像数据设置成一维
image = cv2.imread(imagepath) # cv2.imread方法读取数据
image = cv2.resize(image, (32, 32)).flatten() # 将读取的图片数据设置成相同的大小
data.append(image)
# 读取标签
label = imagepath.split(os.path.sep)[-2]
labels.append(label)
# scale图像数据
data = np.array(data, dtype="float") / 255.0
labels = np.array(labels)
# 数据集切分
(trainx, testx, trainy, testy) = train_test_split(data,
labels, test_size=0.25, random_state=42)
# 转换标签,one-hot格式
lb = labelbinarizer()
trainy = lb.fit_transform(trainy)
testy = lb.transform(testy)
# 网络模型结构 3072-512-256-3
model = sequential()
# kernel_regularizer=regularizers.12(0.01)
# keras.initializers.truncatednormal(mean=0.0, stddex=0.05, seed=none)
# initializers.random_normal
# #model.add(dropout(0.8))
model.add(keras.input(shape=(3072,)))
model.add(dense(512, activation="relu"))
model.add(dense(256, activation="relu"))
model.add(dense(len(lb.classes_), activation="softmax",))
# 初始化参数
init_lr = 0.01
epochs = 200
# 给定损失函数和评估方法
print("[info] 准备训练网络...")
opt = sgd(lr=init_lr)
model.compile(loss="categorical_crossentropy", optimizer=opt,
metrics=["accuracy"])
# 训练网络模型
h = model.fit(trainx, trainy, validation_data=(testx,testy),
epochs=epochs, batch_size=32)
# 测试网络模型
print("[info] 正在评估模型")
predictions = model.predict(testx, batch_size=32)
print(classification_report(testy.argmax(axis=1),
predictions.argmax(axis=1), target_names=lb.classes_))
# 调试完成后,绘制结果曲线
n = np.arange(0, epochs)
plt.style.use("ggplot")
plt.figure()
plt.plot(n, h.history["loss"], label="train_loss")
plt.plot(n, h.history["val_loss"], label="val_loss")
plt.plot(n, h.history["accuracy"], label="train_acc")
plt.plot(n, h.history["val_accuracy"], label="val_acc")
plt.title("training loss and accuracy (simple nn)")
plt.xlabel("epoch #")
plt.ylabel("loss/accuracy")
plt.legend()
plt.savefig('./output/simple_nn_plot.png') # 参数为保存的路径
# 保存模型到本地
print("[info] 正在保存模型")
model.save(args["model"])
f = open('./output/simple_nn_lb.pickle', "wb") # 保存标签数据
f.write(pickle.dumps(lb))
f.close()
utlis_paths.py:
import os
image_types = (".jpg", ".jpeg", ".png", ".bmp", ".tif", ".tiff")
def list_images(basepath, contains=none):
# 返回有效的图片路径数据集
return list_files(basepath, validexts=image_types, contains=contains)
def list_files(basepath, validexts=none, contains=none):
# 遍历图片数据目录,生成每张图片的路径
for (rootdir, dirnames, filenames) in os.walk(basepath):
# 循环遍历当前目录中的文件名
for filename in filenames:
# if the contains string is not none and the filename does not contain
# the supplied string, then ignore the file
if contains is not none and filename.find(contains) == -1:
continue
# 通过确定.的位置,从而确定当前文件的文件扩展名
ext = filename[filename.rfind("."):].lower()
# 检查文件是否为图像,是否应进行处理
if validexts is none or ext.endswith(validexts):
# 构造图像路径
imagepath = os.path.join(rootdir, filename)
yield imagepath
相关参数介绍参考文章:
在训练集上的accuracy为1.0,与测试集上的accuracy差距过大,可能出现过拟合现象。
初始学习率为0.01(学习率过大可能产生过拟合现象)
将学习率改为0.001:
# 初始化参数
init_lr = 0.001
epochs = 200
训练结果:
仍有过拟合现象,但是相较于学习率lr=0.01的情况略有改善。
the dropout
layer randomly sets input units to 0 with a frequency of rate
at each step during training time, which helps prevent overfitting
dropout layer (keras.io)https://keras.io/api/layers/regularization_layers/dropout/
添加drop-out操作:
一般添加在全连接层中
from keras.layers import dropout
model.add(keras.input(shape=(3072,)))
model.add(dense(512, activation="relu"))
model.add(dropout(0.5))
model.add(dense(256, activation="relu"))
model.add(dropout(0.5))
model.add(dense(len(lb.classes_), activation="softmax",))
dropout=0.5,lr=0.001的训练结果:
从结果可以看出,添加dropout层可以有效避免过拟合现象的出现。
查询不同的初始化方法:
layer weight initializers (keras.io)https://keras.io/api/layers/initializers/查看全连接层的参数设置发现,如果不初始化kernel_initializer,起默认的参数为'glorot_uniform',即正态分布初始化,常常不能满足训练的要求。
将kernel_initializer设置为‘randomnormal’,即高斯正太分布:
from keras import initializers
model.add(keras.input(shape=(3072,)))
model.add(dense(512, activation="relu", kernel_initializer=keras.initializers.randomnormal(mean=0.0, stddev=0.05, seed=none)))
# model.add(dropout(0.5))
model.add(dense(256, activation="relu", kernel_initializer=keras.initializers.randomnormal(mean=0.0, stddev=0.05, seed=none)))
# model.add(dropout(0.5))
model.add(dense(len(lb.classes_), activation="softmax", kernel_initializer=keras.initializers.randomnormal(mean=0.0, stddev=0.05, seed=none)))
其中mean为均值,stddev为标准差。
正太高斯分布初始化,不使用dropout层,lr=0.001的训练结果:
将kernel_initializer设置为‘truncatednormal’,即截断正太分布:
model.add(keras.input(shape=(3072,)))
model.add(dense(512, activation="relu", kernel_initializer=keras.initializers.truncatednormal(mean=0.0, stddev=0.05, seed=none)))
# model.add(dropout(0.5))
model.add(dense(256, activation="relu", kernel_initializer=keras.initializers.truncatednormal(mean=0.0, stddev=0.05, seed=none)))
# model.add(dropout(0.5))
model.add(dense(len(lb.classes_), activation="softmax", kernel_initializer=keras.initializers.truncatednormal(mean=0.0, stddev=0.05, seed=none)))
其中mean为均值,stddev为标准差。
截断正太分布初始化,不使用dropout层,lr=0.001的训练结果:
在此基础上再加上dropout层的结果:
regularizers allow you to apply penalties on layer parameters or layer activity during optimization. these penalties are summed into the loss function that the network optimizes.
layer weight regularizers (keras.io)https://keras.io/api/layers/regularizers/
在全连接层添加正则化:
from keras import regularizers
model.add(keras.input(shape=(3072,)))
model.add(dense(512, activation="relu", kernel_initializer=keras.initializers.truncatednormal(mean=0.0, stddev=0.05, seed=none), kernel_regularizer=regularizers.l2(0.01)))
model.add(dropout(0.5))
model.add(dense(256, activation="relu", kernel_initializer=keras.initializers.truncatednormal(mean=0.0, stddev=0.05, seed=none), kernel_regularizer=regularizers.l2(0.01)))
model.add(dropout(0.5))
model.add(dense(len(lb.classes_), activation="softmax", kernel_initializer=keras.initializers.truncatednormal(mean=0.0, stddev=0.05, seed=none), kernel_regularizer=regularizers.l2(0.01)))
有两种正则化方法,l1为取绝对值,l2是求权重的欧几里得范数(常用l2),括号里的参数为惩罚力度(越大的惩罚力度,过拟合的风险越低)。
惩罚力度为0.01正则化,截断正态分布初始化,加入dropout=0.5,lr=0.001的训练结果:
惩罚力度为0.1正则化,截断正态分布初始化,加入dropout=0.5,lr=0.001的训练结果:
保存模型到本地:
保存的模型:惩罚力度为0.1正则化,截断正态分布初始化,加入dropout=0.5,lr=0.001,epochs=1000.
# 保存模型到本地
print("[info] 正在保存模型")
model.save(args["model"])
f = open('./output/simple_nn_lb.pickle', "wb") # 保存标签数据
f.write(pickle.dumps(lb))
f.close()
编写一个predict.py程序来加载模型进行测试:
# 导入所需工具包
from keras.models import load_model
import argparse
import pickle
import cv2
# 加载测试数据并进行相同预处理操作
image = cv2.imread('./cs_image/dog.jpeg') # 读取一张图片进行测试 dog/cat/sheet
output = image.copy() # 复制图片的一份副本给output
image = cv2.resize(image, (32, 32))
# scale图像数据
image = image.astype("float") / 255.0
# 对图像进行拉平操作
image = image.flatten()
image = image.reshape((1, image.shape[0]))
# 读取模型和标签
print("------读取模型和标签------")
model = load_model('./output/simple_nn.model') # 设置模型
lb = pickle.loads(open('./output/simple_nn_lb.pickle', "rb").read()) # 设置标签
# 预测
preds = model.predict(image)
# 得到预测结果以及其对应的标签
i = preds.argmax(axis=1)[0]
label = lb.classes_[i]
# 在图像中把结果画出来
text = "{}: {:.2f}%".format(label, preds[0][i] * 100)
cv2.puttext(output, text, (10, 30), cv2.font_hershey_simplex, 0.7,(0, 0, 255), 2)
# 绘图
cv2.imshow("image", output)
cv2.waitkey(0)
若出现下述报错
error: (-2:unspecified error) the function is not implemented. rebuild the library with windows, gtk+ 2.x or cocoa support.
在对应的虚拟环境中安装opencv的拓展开发包即可解决
pip install opencv-contrib-python -i https://pypi.tuna.tsinghua.edu.cn/simple/
测试的结果:
发现对于猫的预测结果不准确,对于图片的预测,一般会用到卷积神经网络而不是本项目中构建的传统神经网络。
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论