天池人脸识别项目复现

1 项目背景

#c 概述 项目的目的
图像分类是整个计算机视觉领域中最基础的任务,也是最重要的任务之⼀,最适合拿来进⾏学习实践。为了让新⼿们能够⼀次性体验⼀个⼯业级别的图像分类任务的完整流程,本次我们选择带领⼤家完成⼀个对图片中⼈脸进⾏表情识别的任务本任务只对嘴唇部分的表情进⾏识别,所以我们的目标就是获取人脸嘴唇区域的图像,然后进行分类

#c 前景 项目的应用前景
⼈脸表情识别(facial expression recognition, FER)作为⼈脸识别技术中的⼀个重要组成部分,近年来在⼈机交互、安全、机器⼈制造、⾃动化、医疗、通信和驾驶领域得到了⼴泛的关注,成为学术界和⼯业界的研究热点,是⼈脸属性分析的重点。

#c 总结 项目总结

训练步骤
通过该项目,初步对「深度学习」有了实践经验。整个学习的流程有四个步骤,分别是

  1. 「数据收集」,收集需要的数据,可以通过爬虫的方式去收集自己想要的数据,相关资料【杂谈】深度学习必备,各路免费爬虫一举拿下。

  2. 「数据处理」把收集到的数据处理成需要的格式,筛选掉不合格的数据和相关的脏数据。或者进一步提取数据。

  3. 「模型训练」搭建寻训练的模型,准备模型的数据接口,然后进行模型的训练。

  4. 「模型测试」对训练好额模型进行测试。

相关知识
在实践过程当中,学习一些相关的概念知识,整理到了第五点中。有「级联选择器」,「字典推导式」,「损失函数」,「优化器」,「学习率调度器」。

需继续学习的技术
需要继续深入学习「深度学习理论」,「Numpy」,「matplotlib」,「OpenCV」,「Pytorch」。

2 数据获取

#e 爬取数据的方法
使用爬虫工具获取图片的数据
本项目使用的爬虫项目是:https://github.com/sczhengyabin/Image-Downloader ,可以按要求爬取百度、Bing、Google 上的图片。

#c 问题 问题与解决方法

  1. Ubuntu中出现xcd的问题?
    尝试了网上的各种方法,最后改用windows上进行

3 数据处理

#c 解释 数据处理的重要性
爬取得到的数据是⽐较脏的,需要进⾏整理,主要包括统⼀图⽚后缀和重命名。统⼀后缀格式可以减少以后写数据 API 时的压⼒,也可以测试图⽚是不是可以正常的读取,及时防⽌未知问题的出现,这很重要。

3.1 图片格式统一

#e 数据处理的代码

'''
该py文件用一个函数listfiles于将指定目录下的所有图片文件转换为jpg格式的图片文件并删除原图片文件。
参数:rootDir,表示要转换的图片文件所在的目录
返回值:无
总结:
1.使用os.walk()函数遍历指定目录下的所有文件和子目录。
2.使用cv2.imread()函数读取图片文件。
3.使用cv2.imwrite()函数保存图片文件。
4.使用os.remove()函数删除原图片文件。
'''import os
import cv2
def listfiles(rootDir):list_dirs = os.walk(rootDir)'''用于生成文件夹中的文件名通过在目录树中游走。这个函数返回一个「三元组」(dirpath, dirnames, filenames)。dirpath是一个字符串,表示当前正在遍历的目录的路径。dirnames是一个列表,包含了dirpath下所有子目录的名字。filenames是一个列表,包含了非目录文件的名字。这行代码中的list_dirs将会是一个「迭代器」,每次迭代返回上述的三元组,表示rootDir目录及其所有子目录中的文件和目录信息。'''for root, dirs, files in list_dirs:for d in dirs:print(os.path.join(root, d))#打印目录for f in files:fileid = f.split('.')[0]#获取文件名filepath = os.path.join(root, f)#获取文件路径print(filepath)try:#异常处理,确保程序不会因为读取图片出错而中断src = cv2.imread(filepath,1)#读取图片'''cv2.imread()函数读取图片,接收两个参数第一个参数是图片路径,第二个参数是读取图片的方式(1表示读取彩色图片,参数0表示读取灰度图片)返回值是一个numpy数组,表示图片的像素矩阵。'''print("src=",filepath,src.shape)#src.shape是图片的尺寸os.remove(filepath)#删除原图片cv2.imwrite(os.path.join(root,fileid+".jpg"),src)#保存图片'''cv2.imwrite()函数保存图片,接收两个参数第一个参数是保存图片的路径,第二个参数是图片的像素矩阵返回值是一个布尔值,表示是否保存成功'''except:os.remove(filepath)continue
path = "./opencv_facetest"
listfiles(path)

3.2 数据清洗

#c 解释 为何清洗数据
利⽤搜索引擎爬取得到的图⽚肯定有「不符合要求」的,数据清洗主要是「删除不合适」的图⽚,即⾮⼈脸的照⽚。如果利⽤「⼈脸检测算法」仍然⽆法清除⼲净样本,则需要⼿动筛选。当然如果你使⽤多个关键词或者使⽤不同的搜索引擎同样的关键词,或者从视频中提取图⽚,那么爬取回来的图⽚很可能有重复或者⾮常的相似,这样的数据集需要「去重」。

#e 人脸检测算法

'''
使用OpenCV检测图片中的人脸,并显示出来,删除没有人脸的图片
使用到的知识:
1. OpenCV 的人脸检测接口
2. os.listdir() 方法,用于返回指定的文件夹包含的文件或文件夹的名字的列表
3. os.remove() 方法,用于删除文件
4. plt.imshow() 方法,用于显示图片
5. plt.show() 方法,用于显示图片
6. CascadeClassifier.detectMultiScale() 方法,用于检测图片中的人脸
'''import cv2
import os
import matplotlib.pyplot as plt# 人脸检测的接口,这个是 OpenCV 中自带的
cascade_path = './Emotion_Recognition_File/face_detect_model/haarcascade_frontalface_default.xml'
cascade = cv2.CascadeClassifier(cascade_path)
'''
xml文件是OpenCV中的一个训练好的分类器,也就是一个训练好的模型,包含了所有必要的参数和特征,用于快速准确地检测图像中的面部。
'''
# img_path = "./Emotion_Recognition_File/face_det_img/" # 测试图片路径
img_path = "./opencv_facetest" # def DelNoneFaceImage(img_path):"""删除没有人脸的图片"""images = os.listdir(img_path)'''os.listdir() 方法用于返回指定的文件夹包含的文件或文件夹的名字的列表。这个列表以字母顺序。 它不包括 '.' 和'..' 即使它在文件夹中。参数:path,表示要列出的目录返回值:返回指定路径下的文件和文件夹列表'''for image in images:im = cv2.imread(os.path.join(img_path, image), 1) # 读取图片rects = cascade.detectMultiScale(im, 1.3, 5)  # 人脸检测函数'''cascade.detectMultiScale()函数是OpenCV中的一个人脸检测函数,用于检测图片中的人脸。它接收三个参数:im:待检测的图像,通常是灰度图像,因为Haar特征基于灰度值计算。1.3:scaleFactor参数,指定在图像尺寸减小时,搜索窗口的缩放比例。值越大,检测速度越快,但可能错过一些小的或者是距离较远的面部。5:minNeighbors参数,指定每个候选矩形应该保留的邻近矩形的最小数量。这个参数控制着检测的质量,较高的值会减少检测到的假阳性,但也可能错过一些真正的对象。返回值是一个矩形列表,每个矩形表示一个检测到的人脸。'''print("检测到人脸的数量", len(rects))if len(rects) == 0:  # len(rects) 是检测人脸的数量,如果没有检测到人脸的话,会显示出图片,适合本地调试使用,在服务器上可能不会显示os.remove(os.path.join(img_path, image)) # print("删除图片:", image)print()print("删除完成")returndef DetcFaceImage(img_path):"""检测图片中的人脸,并显示出来"""images = os.listdir(img_path)for image in images:im = cv2.imread(os.path.join(img_path, image), 1) # 读取图片rects = cascade.detectMultiScale(im, 1.3, 5)  # 人脸检测函数print("检测到人脸的数量", len(rects))if len(rects) == 0:  # len(rects) 是检测人脸的数量,如果没有检测到人脸的话,会显示出图片,适合本地调试使用,在服务器上可能不会显示passplt.imshow(im[:, :, ::-1])  # 显示'''plt.imshow()函数用于显示图片,接收一个参数,即要显示的图片。由于OpenCV读取的图片是BGR格式,而matplotlib显示的图片是RGB格式,所以需要将BGR格式转换为RGB格式。[:, :, ::-1]表示将图片的第三个维度(颜色通道)反转,即将BGR转换为RGB。'''plt.show()
# DelNoneFaceImage(img_path)
DetcFaceImage(img_path)

3.3 提取嘴唇区域

本任务只对嘴唇部分的表情进⾏识别,所以我们的目标就是获取人脸嘴唇区域的图像,然后进行分类。我们利⽤ Opencv+Dlib 算法提取嘴唇区域, Dlib 算法会得到⾯部的 68 个关键点,我们从中得到嘴唇区域,并适当扩⼤。
人脸 68 点位置图如下:

#c 问题 dlib安装问题

PS E:\Projects\Emotion_Recognition> pip install dlib× Building wheel for dlib (pyproject.toml) did not run successfully.│ exit code: 1╰─> [13 lines of output]running bdist_wheelrunning buildrunning build_extTraceback (most recent call last):ModuleNotFoundError: No module named 'cmake'ERROR: CMake must be installed to build dlib[end of output]note: This error originates from a subprocess, and is likely not a problem with pip.ERROR: Failed building wheel for dlib
Failed to build dlib
ERROR: Could not build wheels for dlib, which is required to install pyproject.toml-based projects

解决方法,安装cmakepip install cmake ,然后指定dlib的版本pip install dlib==19.24.2

#e 提取的代码

下面的代码可以对图片进行人脸检测,检测到人脸后,会将嘴巴区域分割出来,形成数据集。

'''
该py文件用于获取人脸关键点,并根据关键点获取嘴唇区域。
思路:
1.使用Dlib库加载预训练的人脸关键点检测模型。
2.使用OpenCV加载人脸检测器。
3.遍历指定目录下的所有图片文件。
4.使用OpenCV读取图片。
5.使用Dlib检测人脸关键点。
6.根据关键点获取嘴唇区域。
7.保存嘴唇区域。
'''
import cv2
import dlib
import numpy as np
import os
import matplotlib.pyplot as plt# 配置 Dlib 关键点检测路径
# 文件可以从 http://dlib.net/files/ 下载
PREDICTOR_PATH = "./Emotion_Recognition_File/face_detect_model/shape_predictor_68_face_landmarks.dat"
'''
预训练的人脸关键点检测模型,用于检测人脸的关键点。
'''
predictor = dlib.shape_predictor(PREDICTOR_PATH)
'''
dlib.shape_predictor()函数用于加载一个预训练的人脸关键点检测模型。
这个函数接收一个参数,表示模型文件的路径。
'''
# 配置人脸检测器路径
cascade_path = './Emotion_Recognition_File/face_detect_model/haarcascade_frontalface_default.xml'
cascade = cv2.CascadeClassifier(cascade_path)def get_landmarks(im):'''函数:获取人脸关键点参数:im,表示输入的图片返回值:返回一个矩阵,表示人脸关键点的坐标'''rects = cascade.detectMultiScale(im, 1.3, 5) # 人脸检测x, y, w, h = rects[0]  # 获取人脸的四个属性值,左上角坐标 x,y 、高宽 w、h#array([[209, 156, 289, 289]])#print(x, y, w, h)rect = dlib.rectangle(int(x), int(y), int(x + w), int(y + h))'''目的:将人脸检测的结果转换为dlib的矩形对象dlib.rectangle()函数用于创建一个矩形对象,表示一个矩形区域。这个函数接收四个参数,分别是矩形的左上角和右下角的坐标。''' return np.matrix([[p.x, p.y] for p in predictor(im, rect).parts()])'''np.matrix()函数用于将列表转换为矩阵。predictor(im, rect):这个函数调用使用predictor对象(之前通过dlib.shape_predictor加载的面部关键点检测器)在图像im中的指定区域rect(一个矩形,通常是通过面部检测得到的)上检测面部关键点。predictor函数返回一个包含检测到的面部关键点的对象。.parts():这个方法被调用来获取检测到的所有面部关键点。它返回一个包含多个点的对象,每个点代表一个关键点的位置。[p.x, p.y for p in ...]:这是一个列表推导式,用于遍历parts()返回的所有关键点p,并为每个关键点创建一个包含其x和y坐标的列表。np.matrix([...]):这将上一步得到的坐标列表转换成一个NumPy矩阵。NumPy矩阵是一个二维数组,这里每一行代表一个关键点的x和y坐标'''def annotate_landmarks(im, landmarks):'''目的:在图片上标记关键点参数:im,表示输入的图片landmarks,表示关键点坐标'''im = im.copy()# 复制图片for idx, point in enumerate(landmarks):#enumerate()函数用于将一个可遍历的数据对象组合为一个索引序列,同时列出数据和数据下标pos = (point[0, 0], point[0, 1])cv2.putText(im,str(idx),# 文本内容pos,# 位置fontFace=cv2.FONT_HERSHEY_SCRIPT_SIMPLEX,# 字体fontScale=0.4,# 字体大小color=(0, 0, 255))# 颜色cv2.circle(im, pos, 5, color=(0, 255, 255))# 画圆return imdef getlipfromimage(im, landmarks):'''功能:获取嘴唇区域参数:im,表示输入的图片landmarks,表示关键点坐标返回值:返回一个矩形区域,表示嘴唇区域'''xmin = 10000xmax = 0ymin = 10000ymax = 0# 根据最外围的关键点获取包围嘴唇的最小矩形框# 68 个关键点是从# 左耳朵0 -下巴-右耳朵16-左眉毛(17-21)-右眉毛(22-26)-左眼睛(36-41)# 右眼睛(42-47)-鼻子从上到下(27-30)-鼻孔(31-35)# 嘴巴外轮廓(48-59)嘴巴内轮廓(60-67)for i in range(48, 67):'''for循环遍历关键点,找到嘴唇区域的最小矩形框'''x = landmarks[i, 0]y = landmarks[i, 1]if x < xmin:xmin = xif x > xmax:xmax = xif y < ymin:ymin = yif y > ymax:ymax = yprint("xmin=", xmin)print("xmax=", xmax)print("ymin=", ymin)print("ymax=", ymax)roiwidth = xmax - xmin# 嘴唇区域的宽度roiheight = ymax - ymin# 嘴唇区域的高度roi = im[ymin:ymax, xmin:xmax, 0:3]'''im[ymin:ymax, xmin:xmax, 0:3]:这是一个NumPy数组切片操作,用于从原始图像中提取嘴唇区域。它接收三个参数,分别是ymin:ymax、xmin:xmax和0:3。ymin:ymax和xmin:xmax表示切片的范围,0:3表示提取所有的通道(RGB)。该操作返回一个包含嘴唇区域的NumPy数组。'''# 将嘴唇区域扩大1.5倍if roiwidth > roiheight:dstlen = 1.5 * roiwidthelse:dstlen = 1.5 * roiheight# 计算嘴唇区域的中心点diff_xlen = dstlen - roiwidthdiff_ylen = dstlen - roiheightnewx = xminnewy = yminimagerows, imagecols, channel = im.shape'''im.shape是一个元组,包含三个元素,分别是图像的行数、列数和通道数。'''if newx >= diff_xlen / 2 and newx + roiwidth + diff_xlen / 2 < imagecols:newx = newx - diff_xlen / 2elif newx < diff_xlen / 2:newx = 0else:newx = imagecols - dstlenif newy >= diff_ylen / 2 and newy + roiheight + diff_ylen / 2 < imagerows:newy = newy - diff_ylen / 2elif newy < diff_ylen / 2:newy = 0else:newy = imagerows - dstlen'''上述代码:通过智能调整ROI的位置,确保ROI始终位于图像的有效区域内,既不会超出图像边界,也尽可能保持其原始尺寸和形状。这在进行图像处理和分析时非常重要,特别是在需要精确操作图像特定区域的应用中'''roi = im[int(newy):int(newy + dstlen), int(newx):int(newx + dstlen), 0:3]'''使用了Python的切片语法来从图像中提取ROI。int(newy):int(newy + dstlen)和int(newx):int(newx + dstlen)分别定义了ROI在y方向和x方向上的范围。0:3指定了颜色通道的范围,对于一个标准的BGR颜色图像,这意味着选择所有三个颜色通道(蓝色、绿色、红色)'''return roidef listfiles(rootDir):list_dirs = os.walk(rootDir)for root, dirs, files in list_dirs:for d in dirs:print(os.path.join(root, d))for f in files:fileid = f.split('.')[0]filepath = os.path.join(root, f)try:im = cv2.imread(filepath, 1)landmarks = get_landmarks(im)# 获取关键点roi = getlipfromimage(im, landmarks)# 获取嘴唇区域roipath = filepath.replace('.jpg', '_mouth.png')# 保存嘴唇区域
#                 cv2.imwrite(roipath, roi)plt.imshow(roi[:, :, ::-1])plt.show()except:
#                 print("error")continuelistfiles("./Emotion_Recognition_File/face_det_img/")

4 模型搭建训练与测试

4.1 数据接口准备

#c 说明 数据接口准备

直接利用文件夹作为输入,只需要把不同类的数据放到不同的文件夹中。输入一个文件夹,输出图片路径以及标签,在开始训练之前需要将数据集进行拆分,拆分成训练集(train)和验证集(val),训练集和测试集的比例为9:1train_val_data文件结构如下所示,其中 0 代表 none、 1 代表pouting、2 代表 smile、3 代表 openmouth:

#e 准备的代码 数据接口准备

data_transforms = {'train': transforms.Compose([transforms.RandomResizedCrop(48),# 随机裁剪,调整为48*48像素大小,增强泛化transforms.RandomHorizontalFlip(),# 随机水平翻转,增强泛化transforms.ToTensor(),# 转换为张量transforms.Normalize([0.5,0.5,0.5], [0.5,0.5,0.5])# 标准化]),'val': transforms.Compose([transforms.Resize(64),# 调整为64*64像素大小transforms.CenterCrop(48),# 中心裁剪,调整为48*48像素大小transforms.ToTensor(),# 转换为张量transforms.Normalize([0.5,0.5,0.5], [0.5,0.5,0.5])# 标准化]),}data_dir = './Emotion_Recognition_File/train_val_data/' # 数据集所在的位置#创建图像数据集image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x),data_transforms[x]) for x in ['train', 'val']}#创建数据加载器dataloders = {x: torch.utils.data.DataLoader(image_datasets[x],# 数据集batch_size=64,# 每个批次加载64个样本shuffle=True if x=="train" else False,# 训练集打乱,验证集不打乱num_workers=8) for x in ['train', 'val']}# 使用8个子进程加载数据dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'val']}# 数据集大小

4.2 模型定义

#c 说明 模型定义

创建数据接⼝后,开始定义⼀个⽹络 simpleconv3。一个简单的 3 层卷积。在 torch.nn 下,有各种网络层,这里就用到了 nn.Conv2d,nn.BatchNorm2d 和 nn.Linear,分别是卷积层,BN 层和全连接层。我们以一个卷积层为例:

conv1 = nn.Conv2d(in_channels=3, out_channels=12, kernel_size=3, stride=2)
bn1 = nn.BatchNorm2d(num_features=12)
  • in_channels:输入通道数
  • out_channels:输出通道数
  • kernel_size:卷积核的大小
  • stride:卷积核的移动步长

#e 模型定义的代码 模型定义

# 定义一个简单的卷积神经网络
class simpleconv3(nn.Module):def __init__(self):super(simpleconv3,self).__init__()#调用父类的构造函数self.conv1 = nn.Conv2d(3, 12, 3, 2)#定义第一个卷积层self.bn1 = nn.BatchNorm2d(12)#定义第一个批标准化层self.conv2 = nn.Conv2d(12, 24, 3, 2)#定义第二个卷积层self.bn2 = nn.BatchNorm2d(24)#定义第二个批标准化层self.conv3 = nn.Conv2d(24, 48, 3, 2)#定义第三个卷积层self.bn3 = nn.BatchNorm2d(48)#定义第三个批标准化层self.fc1 = nn.Linear(48 * 5 * 5 , 1200)#定义第一个全连接层self.fc2 = nn.Linear(1200 , 128)#定义第二个全连接层self.fc3 = nn.Linear(128 , 4)#定义第三个全连接层def forward(self , x):#前向传播x = F.relu(self.bn1(self.conv1(x)))#卷积层->批标准化层->激活函数#print "bn1 shape",x.shapex = F.relu(self.bn2(self.conv2(x)))#卷积层->批标准化层->激活函数x = F.relu(self.bn3(self.conv3(x)))x = x.view(-1 , 48 * 5 * 5) #reshape操作x = F.relu(self.fc1(x))#全连接层->激活函数x = F.relu(self.fc2(x))x = self.fc3(x)#全连接层return x

4.3 模型训练

#c 说明 测试的意义

  1. 验证模型准确性:通过测试可以验证模型的预测结果是否符合预期,确保模型的准确性和可靠性。
  2. 性能评估:测试可以评估模型在不同条件下的性能,包括处理速度、资源消耗等,以确保模型在实际应用中的效率。
  3. 发现问题:通过对模型进行系统的测试,可以发现模型设计或实现过程中的问题,如过拟合、欠拟合等。
  4. 模型优化:测试结果可以为模型的进一步优化提供依据,帮助开发者调整模型参数,改进模型结构。

#e 训练代码

from __future__ import print_function, divisionimport torch
import torch.nn as nn
import torch.optim as optim
from torch.optim import lr_scheduler
from torch.autograd import Variable
import torchvision
from torchvision import datasets, models, transforms
import time
import os
from tensorboardX import SummaryWriter
import torch.nn.functional as F
import numpy as npimport warnings# 定义一个简单的卷积神经网络
class simpleconv3(nn.Module):def __init__(self):super(simpleconv3,self).__init__()#调用父类的构造函数self.conv1 = nn.Conv2d(3, 12, 3, 2)#定义第一个卷积层self.bn1 = nn.BatchNorm2d(12)#定义第一个批标准化层self.conv2 = nn.Conv2d(12, 24, 3, 2)#定义第二个卷积层self.bn2 = nn.BatchNorm2d(24)#定义第二个批标准化层self.conv3 = nn.Conv2d(24, 48, 3, 2)#定义第三个卷积层self.bn3 = nn.BatchNorm2d(48)#定义第三个批标准化层self.fc1 = nn.Linear(48 * 5 * 5 , 1200)#定义第一个全连接层self.fc2 = nn.Linear(1200 , 128)#定义第二个全连接层self.fc3 = nn.Linear(128 , 4)#定义第三个全连接层def forward(self , x):#前向传播x = F.relu(self.bn1(self.conv1(x)))#卷积层->批标准化层->激活函数#print "bn1 shape",x.shapex = F.relu(self.bn2(self.conv2(x)))#卷积层->批标准化层->激活函数x = F.relu(self.bn3(self.conv3(x)))x = x.view(-1 , 48 * 5 * 5) #reshape操作x = F.relu(self.fc1(x))#全连接层->激活函数x = F.relu(self.fc2(x))x = self.fc3(x)#全连接层return xwarnings.filterwarnings('ignore')#忽略警告writer = SummaryWriter()#创建一个SummaryWriter对象,用于记录训练过程中的损失和准确率def train_model(model, criterion, optimizer, scheduler, num_epochs=25):'''功能:训练神经网络参数:  model,表示要训练的神经网络criterion,表示损失函数optimizer,表示优化器scheduler,表示学习率调度器num_epochs,表示训练的轮次返回值:model,表示训练好的神经网络'''for epoch in range(num_epochs):print('Epoch {}/{}'.format(epoch, num_epochs - 1))# 打印训练次数for phase in ['train', 'val']:#训练和验证if phase == 'train':#训练阶段scheduler.step()#学习率调度器更新学习率model.train(True)  # Set model to training modeelse:model.train(False)  # Set model to evaluate moderunning_loss = 0.0#累计当前阶段的总损失,有助于计算平均损失running_corrects = 0.0#累计当前阶段的总准确率,有助于计算平均准确率for data in dataloders[phase]:#遍历数据加载器inputs, labels = data#获取输入数据和标签if use_gpu:#使用GPUinputs = Variable(inputs.cuda())#将输入数据转换为Variable类型labels = Variable(labels.cuda())#将标签转换为Variable类型else:inputs, labels = Variable(inputs), Variable(labels)optimizer.zero_grad()#进行一次前向传播之前,清零之前的梯度,以防止梯度累积outputs = model(inputs)#将输入数据喂给模型,进行一次前向传播,得到模型的输出。_, preds = torch.max(outputs.data, 1)#从模型输出中找到每个样本预测概率最高的类别作为预测结果。loss = criterion(outputs, labels)#计算模型输出和标签之间的损失if phase == 'train':#训练阶段loss.backward()#进行一次反向传播,计算梯度optimizer.step()#更新模型参数running_loss += loss.data.item()#累计当前阶段的总损失running_corrects += torch.sum(preds == labels).item()#累计当前阶段的总准确率epoch_loss = running_loss / dataset_sizes[phase]#计算平均损失epoch_acc = running_corrects / dataset_sizes[phase]#计算平均准确率if phase == 'train':writer.add_scalar('data/trainloss', epoch_loss, epoch)#记录训练阶段的平均损失writer.add_scalar('data/trainacc', epoch_acc, epoch)#记录训练阶段的平均准确率else:writer.add_scalar('data/valloss', epoch_loss, epoch)#记录验证阶段的平均损失writer.add_scalar('data/valacc', epoch_acc, epoch)#记录验证阶段的平均准确率print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))writer.export_scalars_to_json("./all_scalars.json")#将记录的数据保存为json文件writer.close()#关闭SummaryWriter对象return modelif __name__ == '__main__':data_transforms = {'train': transforms.Compose([transforms.RandomResizedCrop(48),# 随机裁剪,调整为48*48像素大小,增强泛化transforms.RandomHorizontalFlip(),# 随机水平翻转,增强泛化transforms.ToTensor(),# 转换为张量transforms.Normalize([0.5,0.5,0.5], [0.5,0.5,0.5])# 标准化]),'val': transforms.Compose([transforms.Resize(64),# 调整为64*64像素大小transforms.CenterCrop(48),# 中心裁剪,调整为48*48像素大小transforms.ToTensor(),# 转换为张量transforms.Normalize([0.5,0.5,0.5], [0.5,0.5,0.5])# 标准化]),}data_dir = './Emotion_Recognition_File/train_val_data/' # 数据集所在的位置#创建图像数据集image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x),data_transforms[x]) for x in ['train', 'val']}#创建数据加载器dataloders = {x: torch.utils.data.DataLoader(image_datasets[x],# 数据集batch_size=64,# 每个批次加载64个样本shuffle=True if x=="train" else False,# 训练集打乱,验证集不打乱num_workers=8) for x in ['train', 'val']}# 使用8个子进程加载数据dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'val']}# 数据集大小use_gpu = torch.cuda.is_available()# 是否使用GPUprint("是否使用 GPU", use_gpu)modelclc = simpleconv3()# 创建模型print(modelclc)if use_gpu:modelclc = modelclc.cuda()# 使用GPU#配置神经网络训练过程中的损失函数、优化器和学习率调度器的。criterion = nn.CrossEntropyLoss()#选择交叉熵损失函数optimizer_ft = optim.SGD(modelclc.parameters(), lr=0.1, momentum=0.9)'''随机梯度下降(SGD)优化器,用于更新模型的权重。modelclc.parameters() 提供了模型中所有可训练的参数。lr=0.1 设置了学习率为 0.1,这是在训练过程中每次参数更新的步长。momentum=0.9 设置了动量为 0.9,这有助于加速优化器在相关方向上的速度,并减少震荡。'''exp_lr_scheduler = lr_scheduler.StepLR(optimizer_ft, step_size=100, gamma=0.1)'''这行代码定义了一个学习率调度器,它会在每过 step_size(这里是100个)训练轮次后,将学习率乘以 gamma(这里是0.1)。这意味着每100个训练轮次,学习率会减少到原来的10%。这种方法有助于模型在训练早期快速收敛,并在训练后期通过减小学习率来细化模型参数,以避免过拟合'''modelclc = train_model(model=modelclc,# 模型criterion=criterion,# 损失函数optimizer=optimizer_ft,# 优化器scheduler=exp_lr_scheduler,# 学习率调度器num_epochs=10)  # 这里可以调节训练的轮次#保存模型if not os.path.exists("models"):os.mkdir('models')torch.save(modelclc.state_dict(),'models/model.ckpt')# 保存模型参数

4.4 模型测试

#e 测试代码

import sys
import numpy as np
import cv2
import os
import dlibimport torch
import torch.nn as nn
import torch.optim as optim
from torch.optim import lr_scheduler
from torch.autograd import Variable
import torchvision
from torchvision import datasets, models, transforms
import time
from PIL import Image
import torch.nn.functional as Fimport matplotlib.pyplot as plt
import warningswarnings.filterwarnings('ignore')#忽略警告
class simpleconv3(nn.Module):#定义一个简单的三层卷积神经网络def __init__(self):super(simpleconv3,self).__init__()#复制并使用simpleconv3的父类的初始化方法,即先运行nn.Module的初始化函数self.conv1 = nn.Conv2d(3, 12, 3, 2)#定义conv1函数的是图像卷积层:输入是3个feature,输出是6个feature,kernel是3*3,步长为2self.bn1 = nn.BatchNorm2d(12)self.conv2 = nn.Conv2d(12, 24, 3, 2)self.bn2 = nn.BatchNorm2d(24)self.conv3 = nn.Conv2d(24, 48, 3, 2)self.bn3 = nn.BatchNorm2d(48)self.fc1 = nn.Linear(48 * 5 * 5 , 1200)self.fc2 = nn.Linear(1200 , 128)self.fc3 = nn.Linear(128 , 4)def forward(self , x):x = F.relu(self.bn1(self.conv1(x)))#print "bn1 shape",x.shapex = F.relu(self.bn2(self.conv2(x)))x = F.relu(self.bn3(self.conv3(x)))x = x.view(-1 , 48 * 5 * 5) x = F.relu(self.fc1(x))x = F.relu(self.fc2(x))x = self.fc3(x)return xPREDICTOR_PATH = "./Emotion_Recognition_File/face_detect_model/shape_predictor_68_face_landmarks.dat"
predictor = dlib.shape_predictor(PREDICTOR_PATH)#加载人脸关键点检测模型
cascade_path = './Emotion_Recognition_File/face_detect_model/haarcascade_frontalface_default.xml'
cascade = cv2.CascadeClassifier(cascade_path)#加载人脸检测器if not os.path.exists("results"):os.mkdir("results")def standardization(data):'''功能:标准化数据参数:data:数据返回:标准化后的数据'''mu = np.mean(data, axis=0)#计算每一列的均值,axis=0表示列,axis=1表示行sigma = np.std(data, axis=0)#计算每一列的标准差,axis表示方向return (data - mu) / sigma'''数据按列减去其平均值后,再除以其标准差。这一步是标准化的核心,目的是让处理后的数据每一列的平均值为0,标准差为1。这样处理后的数据符合标准正态分布,有利于后续的数据处理和分析。'''def get_landmarks(im):'''功能:获取人脸关键点参数:im:图像返回:人脸关键点'''rects = cascade.detectMultiScale(im, 1.3, 5)#检测人脸x, y, w, h = rects[0]#获取人脸区域rect = dlib.rectangle(int(x), int(y), int(x + w), int(y + h))#转换为dlib格式的人脸区域return np.matrix([[p.x, p.y] for p in predictor(im, rect).parts()])#获取人脸关键点def annotate_landmarks(im, landmarks):'''功能:标注人脸关键点参数:im:图像landmarks:人脸关键点返回:标注后的图像'''im = im.copy()for idx, point in enumerate(landmarks):pos = (point[0, 0], point[0, 1])cv2.putText(im,str(idx),pos,fontFace=cv2.FONT_HERSHEY_SCRIPT_SIMPLEX,fontScale=0.4,color=(0, 0, 255))cv2.circle(im, pos, 3, color=(0, 255, 255))return imtestsize = 48  # 测试图大小data_transforms = transforms.Compose([transforms.ToTensor(),transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
])
net = simpleconv3()#创建模型实例
net.eval()#评估模式
modelpath = "./models/model.ckpt"  # 模型路径
net.load_state_dict(#加载模型保存的模型权重torch.load(modelpath, map_location=lambda storage, loc: storage))
'''map_location参数用于指定加载模型的设备位置。这里使用了一个lambda函数lambda storage, loc: storage,这意味着无论模型权重文件是在哪个设备上保存的(例如GPU或CPU),都将其加载到当前设备上。这对于跨设备加载模型非常有用,特别是当原始训练环境与当前环境不同时。lambda语法:lambda 参数:表达式'''# 一次测试一个文件
img_path = "./Emotion_Recognition_File/test_img/"
imagepaths = os.listdir(img_path)  # 图像文件夹
for imagepath in imagepaths:im = cv2.imread(os.path.join(img_path, imagepath), 1)try:rects = cascade.detectMultiScale(im, 1.3, 5)#检测人脸x, y, w, h = rects[0]rect = dlib.rectangle(int(x), int(y), int(x + w), int(y + h))landmarks = np.matrix([[p.x, p.y]for p in predictor(im, rect).parts()])except:
#         print("没有检测到人脸")continue  # 没有检测到人脸xmin = 10000xmax = 0ymin = 10000ymax = 0for i in range(48, 67):x = landmarks[i, 0]y = landmarks[i, 1]if x < xmin:xmin = xif x > xmax:xmax = xif y < ymin:ymin = yif y > ymax:ymax = yroiwidth = xmax - xminroiheight = ymax - yminroi = im[ymin:ymax, xmin:xmax, 0:3]if roiwidth > roiheight:dstlen = 1.5 * roiwidthelse:dstlen = 1.5 * roiheightdiff_xlen = dstlen - roiwidthdiff_ylen = dstlen - roiheightnewx = xminnewy = yminimagerows, imagecols, channel = im.shapeif newx >= diff_xlen / 2 and newx + roiwidth + diff_xlen / 2 < imagecols:newx = newx - diff_xlen / 2elif newx < diff_xlen / 2:newx = 0else:newx = imagecols - dstlenif newy >= diff_ylen / 2 and newy + roiheight + diff_ylen / 2 < imagerows:newy = newy - diff_ylen / 2elif newy < diff_ylen / 2:newy = 0else:newy = imagecols - dstlenroi = im[int(newy):int(newy + dstlen), int(newx):int(newx + dstlen), 0:3]#裁剪图像roi = cv2.cvtColor(roi, cv2.COLOR_BGR2RGB)#转换颜色空间将BGR转为RGBroiresized = cv2.resize(roi,#调整图像大小(testsize, testsize)).astype(np.float32) / 255.0imgblob = data_transforms(roiresized).unsqueeze(0)#转换数据格式imgblob.requires_grad = False#不需要梯度,因为是验证阶段而不是训练阶段imgblob = Variable(imgblob)#转换数据格式torch.no_grad()#通过上下文管理器禁用梯度计算predict = F.softmax(net(imgblob))#net(imgblob)是模型的输出,F.softmax()是softmax函数,用于多分类问题print(predict)index = np.argmax(predict.detach().numpy())#获取预测结果,np.argmax()返回概率最大的类别im_show = cv2.imread(os.path.join(img_path, imagepath), 1)im_h, im_w, im_c = im_show.shape#获取图像的高、宽、通道数pos_x = int(newx + dstlen)pos_y = int(newy + dstlen)font = cv2.FONT_HERSHEY_SIMPLEX#设置字体样式cv2.rectangle(im_show, (int(newx), int(newy)),#画矩形框(int(newx + dstlen), int(newy + dstlen)), (0, 255, 255), 2)if index == 0:cv2.putText(im_show, 'none', (pos_x, pos_y), font, 1.5, (0, 0, 255), 2)if index == 1:cv2.putText(im_show, 'pout', (pos_x, pos_y), font, 1.5, (0, 0, 255), 2)if index == 2:cv2.putText(im_show, 'smile', (pos_x, pos_y), font, 1.5, (0, 0, 255), 2)if index == 3:cv2.putText(im_show, 'open', (pos_x, pos_y), font, 1.5, (0, 0, 255), 2)
#     cv2.namedWindow('result', 0)
#     cv2.imshow('result', im_show)cv2.imwrite(os.path.join('results', imagepath), im_show)
#     print(os.path.join('results', imagepath))plt.imshow(im_show[:, :, ::-1])  # 这里需要交换通道,因为 matplotlib 保存图片的通道顺序是 RGB,而在 OpenCV 中是 BGRplt.show()
#     cv2.waitKey(0)
# cv2.destroyAllWindows()

5 相关概念

#d 级联选择器
级联分类器(Cascade Classifier)是一种基于机器学习的对象检测技术,它能够在图像中快速识别出目标对象(如人脸、行人等)。这个概念主要用于解决对象检测问题,特别是在实时应用中,如视频监控、人脸识别等场景。

解决的问题:

  1. 速度与准确性的平衡:级联分类器通过一系列的简单到复杂的分类阶段(称为“级联”)来检测对象,每个阶段使用不同的特征集。这种方法可以快速排除背景区域,仅在可能包含目标对象的区域使用更复杂的特征,从而实现高效率和较高准确性的平衡。
  2. 实时检测:由于其高效性,级联分类器特别适合于需要实时处理的应用,如实时视频分析。

没有这个概念的影响:

  1. 检测速度下降:没有级联分类器,我们可能需要在整个图像上运行复杂的检测算法,这会大大增加计算量,降低检测速度,尤其是在处理高分辨率视频或实时应用时。
  2. 准确性和效率的挑战:缺乏有效的筛选机制可能导致算法在背景区域浪费大量计算资源,同时也可能降低检测的准确性,因为算法需要在更广泛的区域中寻找目标对象。
  3. 实时应用受限:在没有高效检测算法的情况下,很多需要实时反馈的应用(如动态人脸识别系统、实时监控系统)可能难以实现或者性能大打折扣。

#e 视频流中识别人脸 级联选择器

import cv2
# 加载预训练的Haar级联分类器
cascade_path = './haarcascade_frontalface_default.xml'
face_cascade = cv2.CascadeClassifier(cascade_path)#级联选择器# 打开摄像头
cap = cv2.VideoCapture(0)
while True:# 读取一帧图像ret, frame = cap.read()if not ret:break# 将图像转换为灰度图,因为Haar分类器需要灰度图gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)# 使用级联分类器检测图像中的人脸faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))# 为每个检测到的人脸画一个矩形框for (x, y, w, h) in faces:cv2.rectangle(frame, (x, y), (x+w, y+h), (255, 0, 0), 2)# 显示结果图像cv2.imshow('Face Detection', frame)# 按'q'退出循环if cv2.waitKey(1) & 0xFF == ord('q'):break
# 释放摄像头并关闭窗口
cap.release()
cv2.destroyAllWindows()

#e 公园里找朋友 级联选择器

想象一下你在一个拥挤的公园里寻找你的朋友。你不能一次性仔细观察每一个人,这样做效率太低。相反,你可以采用类似级联分类器的策略:

  1. 远距离快速扫描:首先,你从远处开始,快速扫视人群,排除那些明显不符合你朋友特征(比如身高、衣着颜色)的人。
  2. 中距离筛选:接着,对于剩下可能符合的人群,你走近一些,关注更多细节,比如发型或者是特定的配饰,进一步缩小范围。
  3. 近距离确认:最后,对于少数几个高度匹配的目标,你可以走到很近的地方,确认是否是你的朋友。

这个过程就像是一个级联的过滤器,每一步都排除了大量不符合条件的目标,最终快速而准确地找到了你的朋友。

#d 字典推导式

字典推导式是一种简洁的构建字典的方法,它可以通过一个表达式来创建一个字典,其中包含了键值对(key-value pairs)。这个概念主要是为了解决在创建字典时需要通过循环和条件语句来添加键值对的复杂性问题。

没有字典推导式的影响:

  1. 代码冗长:在没有字典推导式的情况下,你可能需要使用循环和条件语句来构建字典,这会使代码变得更长、更复杂。
  2. 效率低下:手动通过循环添加键值对可能会比使用字典推导式慢,特别是在处理大量数据时。
  3. 可读性差:字典推导式提供了一种更清晰、更直观的方式来表达如何从一个序列或者其他数据结构中构建字典,没有它,代码可能会更难理解。

字典推导式对以下概念起作用:

  1. 简洁的代码:它提供了一种更简洁的方法来创建字典。
  2. 数据转换:它可以用来轻松地从一个数据结构(如列表、元组列表等)转换为字典。
  3. 条件过滤:在字典推导式中,可以使用if语句来过滤掉不符合条件的元素,从而只包含满足特定条件的键值对。
  4. 自动化处理:它可以用于自动化地处理数据集合,将其转换为字典形式,特别是在数据预处理和数据分析中非常有用。

总的来说,字典推导式是一种非常有用的工具,它可以使代码更加简洁、高效,并且提高代码的可读性。

#e 字符串转字典 字典推导式

# 字符串列表
words = ['apple', 'banana', 'cherry']
# 使用字典推导式创建一个字典,其中键是列表中的字符串,值是对应字符串的长度
word_lengths = {word: len(word) for word in words}
print(word_lengths)
# 输出{'apple': 5, 'banana': 6, 'cherry': 6}

#d 损失函数

损失函数(Loss Function)是机器学习和深度学习中的一个核心概念,用于衡量模型预测值与真实值之间的差异。其主要目的是指导模型学习,通过最小化损失函数来优化模型参数,使模型的预测更加准确。损失函数是机器学习和深度学习中不可或缺的一部分,它为模型训练提供了方向和目标,是实现模型优化和提高预测准确性的关键。

解决的问题:

  1. 提供反馈信号:损失函数为模型训练提供了反馈信号,指示模型当前的表现如何,以及如何调整参数以改进性能。
  2. 优化目标:它定义了一个明确的优化目标。在训练过程中,通过最小化损失函数,模型能够学习到数据的内在规律和模式。
  3. 适应不同问题:不同的问题可以通过选择合适的损失函数来更好地优化,例如分类问题常用交叉熵损失,回归问题常用均方误差损失。

没有损失函数会导致的结果:

  1. 无法衡量性能:没有损失函数,我们将无法量化模型预测的好坏,也就无法判断模型是否在学习或进步。
  2. 缺乏优化方向:模型训练需要一个明确的目标或标准来进行参数的调整和优化。没有损失函数,模型就没有明确的优化方向,无法进行有效的参数更新。
  3. 无法适应不同问题:不同类型的问题需要不同的优化策略。没有损失函数,就无法为不同的问题设计和选择最合适的优化目标,从而降低模型的适用性和准确性。

线性回归是一种预测连续值的算法。假设我们有一组数据点,我们想要找到一条直线,这条直线能够尽可能地接近所有的数据点。这里,损失函数的作用就是衡量这条直线与实际数据点之间的差距。

#e 均方误差(MSE) 损失函数

假设我们的直线方程为 y = wx + b,其中 w 是权重,b 是偏置。对于每个数据点 (x_i, y_i),直线给出的预测值是 ŷ_i = wx_i + b。均方误差(MSE)损失函数计算所有数据点的真实值 y_i 与预测值 ŷ_i 之间差值的平方和的平均值:

MSE = (1/n) * Σ(y_i - ŷ_i)^2

其中 n 是数据点的总数,Σ 表示求和。通过最小化这个损失函数,我们可以找到最佳的 wb,使得直线尽可能地接近所有的数据点。

#e 减肥 损失函数

假设你想要减肥,你设定了一个目标:在接下来的三个月内减掉10公斤。在这个过程中,你每周都会称一次体重,记录下来,并与你的目标进行比较。

在这个例子中,你的“损失函数”可以看作是每次称重时与目标体重差距的大小。如果这个差距在减小,说明你的减肥计划正在取得进展;如果这个差距在增大或不变,说明你需要调整你的饮食或运动计划。

就像在机器学习中通过调整模型参数来最小化损失函数一样,你也可以通过调整你的饮食习惯和运动计划来“最小化”你的体重差距,更接近你的减肥目标。

#c 补充 相关概念

损失函数会影响的概念:

  1. 优化器(Optimizer):优化器使用损失函数的梯度来更新模型的参数,以减少损失。不同的优化器(如SGD、Adam)可能会对损失函数的下降速度和稳定性产生不同的影响。
  2. 正则化(Regularization):正则化项可以添加到损失函数中,以防止模型过拟合。正则化如L1、L2会影响损失函数的形式和模型的最终性能。
  3. 学习率(Learning Rate):学习率决定了在优化过程中参数更新的步长大小。它直接影响损失函数下降的速度和是否能够收敛到最小值。

影响损失函数的概念:

  1. 模型复杂度(Model Complexity):模型的复杂度决定了其拟合数据的能力。过于复杂的模型可能导致过拟合,这时即使损失函数在训练集上很低,也可能在测试集上表现不佳。
  2. 数据预处理(Data Preprocessing):数据的预处理方式(如标准化、归一化)会影响模型的学习过程,进而影响损失函数的表现。不同的数据分布可能需要不同的损失函数。
  3. 特征工程(Feature Engineering):有效的特征工程可以提高模型的性能,减少损失。选择和构造与预测任务高度相关的特征可以使损失函数更有效地指导模型学习。

#d 优化器

优化器(Optimizer)是机器学习和深度学习中用于更新和调整模型参数(如权重和偏置),以最小化损失函数的算法。优化器的目的是找到损失函数的最小值,从而提高模型的预测准确性。

创造优化器解决的问题:

  1. 参数更新:在模型训练过程中,需要一种方法来决定如何更新模型的参数,以便于模型能够学习到数据的特征和规律。优化器提供了这样一种方法。
  2. 收敛速度:不同的优化算法会影响模型训练的速度。一些优化器能够加快模型收敛的速度,使训练过程更高效。
  3. 避免局部最小值:在复杂的损失函数中,可能存在多个局部最小值。优化器的设计旨在帮助模型避免陷入局部最小值,寻找到全局最小值或较好的局部最小值。

没有优化器会导致的结果:

  1. 无法有效更新模型参数:没有优化器,我们将缺乏一种系统性的方法来调整模型参数,这将导致模型无法从训练数据中学习。
  2. 训练效率低下:即使可以手动调整模型参数,但这种方法效率极低,且难以找到损失函数的最小值,导致训练时间长,效果差。
  3. 模型性能不佳:缺乏有效的优化策略,模型可能无法达到较好的性能,特别是在处理复杂任务和大规模数据集时

#e 深度学习中优化器 优化器

在深度学习模型训练过程中,优化器负责调整模型的权重,以最小化损失函数。假设我们正在训练一个用于图像分类的卷积神经网络(CNN)。我们选择Adam优化器,因为它结合了动量(Momentum)和自适应学习率(AdaGrad)的优点,适合处理大规模数据和参数的优化问题。

import torch
import torch.nn as nn
import torch.optim as optimmodel = MyCNN()  # 假设MyCNN是我们定义的卷积神经网络
criterion = nn.CrossEntropyLoss()  # 选择交叉熵损失函数
optimizer = optim.Adam(model.parameters(), lr=0.001)  # 使用Adam优化器,学习率设置为0.001# 训练模型的简化代码
for epoch in range(num_epochs):for inputs, labels in dataloader:optimizer.zero_grad()  # 清空之前的梯度outputs = model(inputs)  # 前向传播loss = criterion(outputs, labels)  # 计算损失loss.backward()  # 反向传播,计算梯度optimizer.step()  # 更新权重

优化器Adam负责在每次迭代中更新模型的权重,以减少损失函数的值,从而提高模型在图像分类任务上的准确率。

#e 减肥计划 优化器

假设你正在尝试减肥,并设定了一个目标:在三个月内减重8公斤。你的“优化器”就是你制定的减肥计划,包括饮食控制和运动计划。

  • 初始计划:你开始时决定每天跑步30分钟,每周五天,同时减少碳水化合物的摄入。
  • 调整策略:两周后,你发现体重下降速度不如预期,于是你决定增加运动量,改为每天跑步45分钟,并且加入两天的力量训练。
  • 反馈调整:每周通过称重来评估减肥效果,根据体重变化调整饮食和运动计划。

在这个过程中,你不断地根据体重变化(相当于“损失函数”)调整你的减肥计划(“优化器”),以期达到减重的目标。这个过程类似于在机器学习中使用优化器调整模型参数,以最小化损失函数。

#c 关联 相关概念

「优化器」影响的概念:

  1. 学习率(Learning Rate):优化器通过学习率控制参数更新的步长。学习率太高可能导致训练不稳定,太低则训练速度缓慢。
  2. 动量(Momentum):动量帮助优化器在正确的方向上加速,减少震荡,从而更快地收敛。
  3. 正则化(Regularization):虽然正则化通常定义在损失函数中,但它也影响优化器的行为,因为优化器需要考虑正则化项来更新参数,以防止过拟合。
  4. 学习率调度器(Learning Rate Scheduler):调度器根据预设策略调整学习率,优化器需要根据这些调整来更新参数。这有助于在训练的不同阶段采取不同的学习策略。

影响「优化器」的概念:

  1. 损失函数(Loss Function):优化器的目标是最小化损失函数。不同的损失函数可能会导致优化器的行为差异,因为损失函数的形状直接影响梯度的计算。
  2. 模型复杂度(Model Complexity):模型的复杂度决定了优化过程的难度。复杂模型可能有更多的局部最小值,这对优化器的选择和配置提出了挑战。
  3. 数据预处理(Data Preprocessing):数据的规模和分布会影响梯度的计算,进而影响优化器的效率和策略。例如,未经归一化的数据可能导致训练过程不稳定。
  4. 参数初始化(Parameter Initialization):模型参数的初始值可以影响优化器的收敛速度和是否能够达到全局最小值。不同的初始化方法可能会导致训练过程中的不同表现。

#d 学习率调度器

学习率调度器(Learning Rate Scheduler)是一种在训练过程中动态调整学习率的策略。它根据预定的规则或实时的训练指标,逐步调整学习率的大小,以提高模型训练的效率和效果。学习率调度器通过在训练过程中动态调整学习率,帮助模型更有效地学习,提高训练的稳定性和效率,最终获得更好的性能。

创造学习率调度器解决的问题:

  1. 避免学习率过高:如果学习率设置得过高,可能会导致模型参数更新过猛,从而使得「损失函数值」振荡甚至发散,影响模型的稳定性和「收敛速度」。
  2. 避免学习率过低:在训练后期,如果学习率过低,模型参数的更新将非常缓慢,这可能导致训练过程陷入「局部最小值」,或者需要更长的时间来收敛。
  3. 适应训练阶段:不同的训练阶段可能需要不同的学习率。在初期可能需要较高的学习率以快速下降,在接近最优解时则需要较低的学习率以细致调整,避免过度震荡。

没有学习率调度器会导致的结果:

  1. 训练效率低下:固定的学习率可能无法同时满足训练初期和后期的需求,导致训练效率不是最优,甚至需要更多的训练周期才能达到相同的效果。
  2. 模型性能不佳:如果学习率始终过高或过低,模型可能无法收敛到最佳状态,导致最终的模型性能不佳。
  3. 调参困难:没有学习率调度器,开发者可能需要手动多次尝试不同的学习率设置,以找到最佳的学习率,这个过程既耗时又低效。

#e StepLR调度器 学习率调度器

在PyTorch中,学习率调度器用于根据预定策略动态调整学习率。以下是一个使用StepLR调度器的简单例子,它在每过step_size个epoch后,将学习率乘以gamma来减小学习率。

import torch
import torch.optim as optim# 定义一个简单的模型
model = torch.nn.Linear(10, 1)
optimizer = optim.SGD(model.parameters(), lr=0.1)# 使用StepLR学习率调度器
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.1)for epoch in range(100):# 训练模型的代码...# 每个epoch结束后更新学习率scheduler.step()

#e 马拉松训练 学习率调度器

想象你正在为一场马拉松比赛训练。在训练初期,你可能会选择较高的强度(相当于高学习率),以快速提升你的体能和耐力。随着比赛日的临近(接近最优解),你可能会逐渐减少训练强度(降低学习率),进行更多的恢复性训练和技术细节的调整,以避免受伤并确保在比赛日能有最佳表现。

这个过程类似于使用学习率调度器来动态调整训练强度,确保在不同阶段采取最合适的训练策略,以达到最佳的训练效果。

#c 关联 相关概念
学习率调度器的作用和受影响的概念:

会影响的概念:

  1. 模型收敛速度:通过适时调整学习率,学习率调度器可以加快模型的收敛速度,特别是在训练初期通过使用较高的学习率。
  2. 模型性能:适当减小学习率可以帮助模型更细致地适应训练数据,避免「过拟合」,从而提高模型的泛化能力和最终性能。
  3. 训练稳定性:通过避免学习率过高导致的参数更新过猛,学习率调度器有助于保持训练过程的稳定性,减少损失函数的振荡。

影响学习率调度器的概念:

  1. 优化器(Optimizer):学习率调度器直接作用于优化器,调整其学习率参数。不同的优化器(如SGD、Adam等)可能对学习率的调整有不同的敏感度和效果。
  2. 训练策略:训练过程中的策略,如批大小(batch size)、训练轮次(epochs)等,会影响学习率调度器的设置。例如,较大的批大小可能需要不同的学习率调整策略。
  3. 模型复杂度和数据集:模型的复杂度和数据集的特性也会影响学习率调度器的选择和配置。复杂模型或难以拟合的数据集可能需要更精细的学习率调整策略。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/856187.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Tableau系列第(6)篇】使用Tableau Prep进行数据清理、整合(一)

使用Tableau Prep的整体过程详见&#xff1a;【Tableau系列第&#xff08;5&#xff09;篇】用Tableau Prep整理数据全流程初体验 本篇一步一步跟我一起来熟悉更多的Tableau Prep数据清理、整合的操作。 示例excel数据源链接: https://pan.baidu.com/s/17nx3_LPe30oK1l1JsC6K…

04. Java 多线程的创建

1. 前言 本节内容重点需要掌握 Java 多线程的三种创建方式&#xff0c;具体内容如下&#xff1a; Java 线程类 Thread 继承结构&#xff0c;这是 JDK Thread 源码的类结构&#xff0c;是了解 Thread 类的第一步&#xff1b;掌握多线程的三种创建方式&#xff0c;这是本节的重…

记录:[android] SSLHandshakeException: Handshake failed 问题;已解决!

1、问题描述&#xff1a;在使用Retrofit2 时在安卓老设备上&#xff08;安卓6.0&#xff09;网络无法请求、安卓 10 、 11 未出现此问题&#xff1f;what? 原因&#xff1a;服务端 TLS 版本过高 2、废话不多说、解决方案A 、添加依赖&#xff1a;implementation org.conscrypt…

国产MCU芯片(3):小华半导体

前言: 微控制芯片(MCU)作为现代电子化、信息化智能控制的核心元器件之一,是将中央处理器、存储器、定时器/计时器、模拟信号采集模块以及通信接口等集成在一块芯片上的微型控制器。 家电与消费市场端仍是国内的MCU厂家下游最大的应用市场和主战场,最近两年正在发力车规级市…

ATA-4052C高压功率放大器在新能源汽车安全测试中的应用

新能源汽车的崛起已经改变了汽车行业的格局&#xff0c;为环境友好型交通方式提供了更多的选择。为了确保这些新型汽车的安全性和可靠性&#xff0c;进行全面的安全测试是至关重要的。高压功率放大器在新能源汽车的安全测试中发挥着重要的作用&#xff0c;本文将介绍其应用以及…

(2024)豆瓣电影TOP250爬虫详细讲解和代码

&#xff08;2024&#xff09;豆瓣电影TOP250爬虫详细讲解和代码 爬虫目的 获取 https://movie.douban.com/top250 电影列表的所有电影的属性。并存储起来。说起来很简单就两步。 第一步爬取数据第二步存储 爬虫思路 总体流程图 由于是分页的&#xff0c;要先观察分页的规…

力扣1901.寻找峰值II

力扣1901.寻找峰值II 二分每一行 并用函数找出每一行中最大值的下标若最大值比其下面相邻的元素大 则上方一定存在峰值若最大值比其下面相邻的元素小 则下方一定存在峰值 class Solution {int indexmax(vector<int> &a){return max_element(a.begin(),a.end()) - …

【计算机网络仿真】b站湖科大教书匠思科Packet Tracer——实验一 Packet Tracer的简单使用和访问Web服务器

一、实验目的 1.掌握Packet Tracer的简单使用方式&#xff1b; 2.加深对网络体系结构的理解。 二、实验要求 1.搭建Cisco Packet Tracer仿真平台使用环境&#xff1b; 2.观看B站湖科大教书匠仿真实验视频&#xff0c;完成对应实验。 三、实验内容 第一部分:Packet Tracer的…

最实战的GLM4微调入门:从文本分类开始

GLM4是清华智谱团队最近开源的大语言模型。 以GLM4作为基座大模型&#xff0c;通过指令微调的方式做高精度文本分类&#xff0c;是学习LLM微调的入门任务。 使用的9B模型&#xff0c;显存要求相对较高&#xff0c;需要40GB左右。 在本文中&#xff0c;我们会使用 GLM4-9b-Chat…

全志 Android 11:实现响应全局按键

一、篇头 最近实现热键想功能&#xff0c;简单总结了下全志平台Android 11 的响应全局热键的方法。 二、需求 实现全局热键&#xff0c;响应F-、AF、F三个按键&#xff0c;AF只用于启动调焦界面&#xff0c;F-和F除了可以启动调焦界面外&#xff0c;还用于调整镜头的焦距&…

Go web框架|Beego、Gin、Echo、Iris等干货教程

Go 是一门正在快速增长的编程语言&#xff0c;专为构建简单、快速且可靠的软件而设计。golang提供的net/http库已经很好了&#xff0c;对于http的协议的实现非常好&#xff0c;基于此再造框架&#xff0c;也不会是难事&#xff0c;因此生态中出现了很多框架。 本篇文章主要介绍…

使用node把任意网站封装为可执行文件

直接上步骤&#xff1a; 1. node.js 环境准备 下载地址 那个版本都行&#xff0c;下一步->下一步 安装即可 2. windows 系统下&#xff0c; 快捷键 winr ->输入 cmd -> 回车 3. 执行第一个命令&#xff0c;安装 nativefier 等一段时间 npm install nativefier -g 4…

全面赋能,永久免费!讯飞星火API能力正式免费开放

2023年5月&#xff0c;讯飞星火正式发布&#xff0c;迅速成为千万用户获取知识、学习知识的“超级助手”&#xff0c;成为解放生产力、释放想象力的“超级杠杆”。 2024年5月&#xff0c;讯飞星火API能力正式免费开放&#xff0c;携手生态开发者加快大模型赋能刚需场景。 领…

【考研408计算机组成原理】存储系统之Cache考点

苏泽 “弃工从研”的路上很孤独&#xff0c;于是我记下了些许笔记相伴&#xff0c;希望能够帮助到大家 另外&#xff0c;利用了工作之余的一点点时间&#xff0c;整理了一套考研408的知识图谱&#xff0c; 我根据这一套知识图谱打造了这样一个408知识图谱问答系统 里面的每一…

【C++题解】1324 - 扩建鱼塘问题

问题&#xff1a;1324 - 扩建鱼塘问题 类型&#xff1a;分支问题 题目描述&#xff1a; 有一个尺寸为 mn 的矩形鱼塘&#xff0c;请问如果要把该鱼塘扩建为正方形&#xff0c;那么它的面积至少增加了多少平方米&#xff1f; 输入&#xff1a; 两个整数 m 和 n 。 输出&…

LeetCode 54.螺旋矩阵

1.题目要求如图所示: 各位看官们&#xff0c;大家好呀&#xff0c;今天小编用的方法比较麻烦&#xff0c;就是按顺时针遍历&#xff0c;但也挺好理解的&#xff0c;因为就是迭代法循环&#xff0c;所以就不给大家讲步骤了&#xff0c;直接就发代码了: /*** Note: The returne…

深入浅出Netty:高性能网络应用框架的原理与实践

深入浅出Netty&#xff1a;高性能网络应用框架的原理与实践 1. Netty简介 Netty是一个基于Java的异步事件驱动的网络应用框架&#xff0c;广泛用于构建高性能、高可扩展性的网络服务器和客户端。它提供对多种协议&#xff08;如TCP、UDP、SSL等&#xff09;的支持&#xff0c;…

【计算机网络篇】数据链路层(11)在数据链路层扩展以太网

文章目录 &#x1f354;使用网桥在数据链路层扩展以太网&#x1f95a;网桥的主要结构和基本工作原理&#x1f388;网桥的主要结构&#x1f50e;网桥转发帧的例子&#x1f50e;网桥丢弃帧的例子&#x1f50e;网桥转发广播帧的例子 &#x1f95a;透明网桥&#x1f50e;透明网桥的…

网络基础篇:网络模型

目录 一、初识网络 二、网络的分层 OSI七层模型 TCP/IP四层模型 网络与系统的关系 网络传输基本流程 数据包封装和分用 三、IP地址与MAC地址 认识IP地址 认识MAC地址 IP与MAC的关系 一、初识网络 同一台设备上的进程间通信有很多种方式 &#xff1a; 管道&#xff08…

需求虽小但是问题很多,浅谈JavaScript导出excel文件

最近我在进行一些前端小开发&#xff0c;遇到了一个小需求&#xff1a;我想要将数据导出到 Excel 文件&#xff0c;并希望能够封装成一个函数来实现。这个函数需要接收一个二维数组作为参数&#xff0c;数组的第一行是表头。在导出的过程中&#xff0c;要能够确保避免出现中文乱…