乌兰推
IP:
0关注数
0粉丝数
0获得的赞
工作年
编辑资料
链接我:

创作·39

全部
问答
动态
项目
学习
专栏
乌兰推

【Opencv入门到项目实战】(八):图像直方图计算

1.图像直方图1.1 直方图计算直方图是一种用于可视化数据分布的图表形式,它显示了数据在各个数值范围内的频率或数量。直方图可以帮助我们了解数据的分布情况、寻找异常值和识别数据模式。在计算机视觉中,直方图也经常用于图像处理和分析。通过统计图像中不同灰度级别的像素数量,可以获得图像的灰度分布,进而进行图像增强、对比度调整、颜色校正等操作。首先每个图像单独拿出来其实就是一堆像素点,范围在0-255之间,而直方图就是统计这一张图片各个像素点值的个数,如下图所示,横坐标代表不同像素点取值,纵坐标代表出现的次数。在Opencv中,可以使用cv2.calcHist()函数来计算图像的直方图。语法如下:cv2.calcHist(images,channels,mask,histSize,ranges) cv2.calcHist()函数的各个参数解释:images: 需要计算直方图的图像,以方括号的形式传递,表示可以处理多个图像。例如[img]channels: 指定计算直方图的通道索引,对于灰度图像,只有一个通道,因此传递0。如果是彩色图像,则传入的参数可以是 0,1,2,它们分别对应着 BGR。mask: 用于指定掩码图像,如果不需要,则设置为None,即统计整幅图像的直方图Size: 表示直方图的大小,即有多少个bins,例如设置为256,表示将图像像素值分成256个区间进行统计。ranges表示像素值的范围。常为 [0, 256]接下来我们来看一下具体的案例:我们照样是读取小狗洋气的照片,来看一下它的直方图分布import cv2 #opencv读取的格式是BGR ​ # 定义图像显示函数 def cv_show(img,name):    cv2.imshow(name,img)    cv2.waitKey()    cv2.destroyAllWindows()     img = cv2.imread('yangqi.jpg',0) #0表示读取灰度图 ​ # 直方图计算 hist = cv2.calcHist([img],[0],None,[256],[0,256]) hist.shape (256, 1) hist返回每个0-255共256个像素值出现的个数,我们打印出来看一下它的分布情况plt.hist(img.ravel(),256); plt.show() 1.2 分通道读取接下来我们以彩色图为例,统计每一个通道的直方图。这里要注意的是,opencv的颜色顺序是b、g、r。接下来我们首先读取彩色图,然后利用一个for循环来遍历每个通道得到对应的直方图并打印。# 读取彩色图 img = cv2.imread('yangqi.jpg') ​ # 直方图 color = ('b','g','r') for i,col in enumerate(color):    histr = cv2.calcHist([img],[i],None,[256],[0,256])    plt.plot(histr,color = col)    plt.xlim([0,256]) 1.3 mask操作(掩码操作)直方图掩码操作是一种通过应用掩码图像来选择特定区域,并计算该区域的直方图的方法。它在图像处理和计算机视觉领域中非常常用,用于分析图像中感兴趣区域的像素分布情况。在直方图掩码操作中,我们使用两个图像:原始图像和掩码图像。原始图像:这是我们要进行操作和分析的图像。掩码图像:这是一个二值图像,用于指定原始图像中我们感兴趣的区域。掩码图像通常是灰度图像,其中具有我们感兴趣的区域的像素值设为255,而其他区域的像素值为零。我们先来创建一个掩码图像# 创建mast mask = np.zeros(img.shape[:2], np.uint8) print (mask.shape) mask[100:200, 50:150] = 255 cv_show(mask,'mask') 接下来我们应用掩码:使用OpenCV的cv2.bitwise_and()函数将原始图像和掩码图像进行操作。产生一个新的图像,其中只有掩码图像非零像素对应的原始图像像素会被保留下来,其余像素置为零,相当于起到了一个捕捉特定区域的作用masked_img = cv2.bitwise_and(img, img, mask=mask)#掩码操作 cv_show(masked_img,'masked_img') 计算直方图:使用OpenCV的cv2.calcHist()函数计算掩码后图像的直方图。可以指定通道索引,直方图大小和像素值范围等参数。hist_full = cv2.calcHist([img], [0], None, [256], [0, 256]) hist_mask = cv2.calcHist([img], [0], mask, [256], [0, 256]) 绘制直方图:使用Matplotlib库绘制直方图,并通过plt.show()显示图像。plt.subplot(221), plt.imshow(img, 'gray') plt.subplot(222), plt.imshow(mask, 'gray') plt.subplot(223), plt.imshow(masked_img, 'gray') plt.subplot(224), plt.plot(hist_full), plt.plot(hist_mask) plt.xlim([0, 256]) plt.show() 通过直方图掩码操作,我们可以获取特定区域的像素分布情况,从而对图像进行更精细的分析和处理。这种方法常用于目标检测、图像分割、背景建模等应用中,使得我们可以专注于感兴趣的区域并获得相关的统计信息。2.傅里叶变换傅里叶变换(Fourier Transform)是一种将信号在时域(时间域)和频域(频率域)之间进行转换的数学工具,具体的数学理论不做详细探讨。它的基本思想是将一个信号分解成一组不同频率的正弦和余弦函数的叠加。在图像处理中,傅里叶变换常被用于分析和处理图像的频域信息,通过将图像从空间域转换到频域,我们可以了解图像中各个频率成分的贡献,针对不同频率,我们有以下作用:高频:变化剧烈的灰度分量,例如边界低频:变化缓慢的灰度分量2.1 频率转换结果对于二维图像而言,我们常用离散傅里叶变换(Discrete Fourier Transform, DFT)离散傅里叶变换(DFT)是将图像从空间域转换到频域的一种方法,它通过计算图像中每个像素点的频谱来描述图像的频率特征。DFT输出的结果是一个复数数组,其中每个元素表示该频率分量的振幅和相位。因此,我们需要将这个输出结果转换成图像格式。在opencv中我们调用cv2.dft()函数实现dft转换,输入图形要先转换成np.float32的格式,此外,cv2.dft()返回的结果是双通道的(实部,虚部),通常还需要转换成图像格式才能展示(0,255)。import numpy as np import cv2 from matplotlib import pyplot as plt img = cv2.imread('lena.jpg',0) img_float32 = np.float32(img) dft = cv2.dft(img_float32, flags = cv2.DFT_COMPLEX_OUTPUT) # 使用dft dft_shift = np.fft.fftshift(dft)#shift转换,将频率为0的部分转换到中心位置。 # 得到灰度图能表示的形式 magnitude_spectrum = 20*np.log(cv2.magnitude(dft_shift[:,:,0],dft_shift[:,:,1])) plt.subplot(121),plt.imshow(img, cmap = 'gray') plt.title('Input Image'), plt.xticks([]), plt.yticks([]) plt.subplot(122),plt.imshow(magnitude_spectrum, cmap = 'gray') plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([]) plt.show() 右图是经过频率转换之后的结果,可以看到中心位置比较亮,呈现一个中心向外发散的情况,越在中心频率越低,越在外边频率越高2.2 高通和低通滤波器低通滤波器和高通滤波器是信号处理中常见的两种滤波器类型,用于在频域上选择特定频率范围内的信号分量。低通滤波器(Low-pass Filter)的作用是保留信号中较低频率的分量,并削弱或消除高频分量,相当于把边界抹除了,使得图像变得模糊。低通滤波器可以用来平滑信号、去除噪声或降低信号频率。 低通滤波器的频率响应通常是一个平坦的直线,直到截止频率,截止频率之后,频率响应开始逐渐下降。我们下面来看一下如何实现一个经过低通滤波器之后再返回源图像import numpy as np import cv2 from matplotlib import pyplot as plt img = cv2.imread('lena.jpg',0) img_float32 = np.float32(img) dft = cv2.dft(img_float32, flags = cv2.DFT_COMPLEX_OUTPUT) #dft结果 dft_shift = np.fft.fftshift(dft) #使用shift将0转换到中心位置 rows, cols = img.shape crow, ccol = int(rows/2) , int(cols/2) # 中心位置 # 低通滤波 mask = np.zeros((rows, cols, 2), np.uint8) mask[crow-30:crow+30, ccol-30:ccol+30] = 1 #只在中心位置为1,其余位置都为0 # IDFT:DFT的逆变换,将dft的结果变换的源图像 fshift = dft_shift*mask #通过掩码操作只保留中心位置 f_ishift = np.fft.ifftshift(fshift)# 做shift的逆变换 img_back = cv2.idft(f_ishift)# 做dft的逆变换 img_back = cv2.magnitude(img_back[:,:,0],img_back[:,:,1])#处理成图像的格式 plt.subplot(121),plt.imshow(img, cmap = 'gray') plt.title('Input Image'), plt.xticks([]), plt.yticks([]) plt.subplot(122),plt.imshow(img_back, cmap = 'gray') plt.title('Result'), plt.xticks([]), plt.yticks([]) plt.show() 左图是我们的原始图像,右图是经过低通道后得到的图像结果,可以看到图像变得模糊,并且边界点不是很明显,主要集中在中心人脸位置。高通滤波器(High-pass Filter): 高通滤波器允许通过频率范围内的高频信号,而抑制低频信号,相当于将边界锐化,会使得图像细节增强。它的作用是保留信号中的高频分量,并减弱或消除低频信号。高通滤波器可以用来强调信号中的快速变化部分,滤除基线漂移或去除低频噪声。import numpy as np import cv2 from matplotlib import pyplot as plt img = cv2.imread('lena.jpg',0) img_float32 = np.float32(img) dft = cv2.dft(img_float32, flags = cv2.DFT_COMPLEX_OUTPUT) #dft结果 dft_shift = np.fft.fftshift(dft) #使用shift将0转换到中心位置 rows, cols = img.shape crow, ccol = int(rows/2) , int(cols/2) # 中心位置 # 高通滤波 mask = np.ones((rows, cols, 2), np.uint8) mask[crow-30:crow+30, ccol-30:ccol+30] = 0 #只在中心位置为0,其余位置都为1 # IDFT:DFT的逆变换,将dft的结果变换的源图像 fshift = dft_shift*mask #通过掩码操作只保留中心位置 f_ishift = np.fft.ifftshift(fshift)# 做shift的逆变换 img_back = cv2.idft(f_ishift)# 做dft的逆变换 img_back = cv2.magnitude(img_back[:,:,0],img_back[:,:,1])#处理成图像的格式 plt.subplot(121),plt.imshow(img, cmap = 'gray') plt.title('Input Image'), plt.xticks([]), plt.yticks([]) plt.subplot(122),plt.imshow(img_back, cmap = 'gray') plt.title('Result'), plt.xticks([]), plt.yticks([]) plt.show() 可以看到图像只保留了一些边界信息,主要是Lena的身体轮廓。最后我们来思考一下为什么要使用傅里叶变换呢,为什么我们要把图像转换到一个频率当中做处理呢?其实最主要的原因就是这样做更简单高效。经过傅里叶变换,这个频率当中分为低频和高频,层次分明,此时做各种变换都特别容易。如果在原始图像中进行变换,会非常复杂,不利于我们工程去实现迭代。因此,我们通常都会把图像先映射到频率当中,在频率当中做处理。
0
0
0
浏览量270
乌兰推

【Opencv入门到项目实战】(七):图像轮廓计算

引言这一篇文章我们来讨论图像轮廓相关的知识点,什么叫做轮廓,从定义上来说它是指图像中连续的曲线或边界,表示了图像中目标的形状和外观特征。我们之前已经介绍了一些图像边缘检测相关的内容,我们有一些零零散散的一些线段也可以当做是一个边缘,但是这些不能看做是图像轮廓,因为轮廓首先得是一个整体,是连在一块的,这就是轮廓和边缘之间的区别。我们只要记住:边缘: 零零散散的轮廓: 它是一个整体1.​轮廓检测接下来我们首先要看的的就是轮廓检测,我们调用OpenCV的 cv2.findContours() 函数来查找图像中的轮廓。这个函数需要输入一个二值化的图像(通常是经过边缘检测后的结果),并返回一组轮廓。可以选择不同的检索模式和轮廓逼近方法来控制轮廓的提取方式。语法如下:cv2.findContours(image,mode,method) image:输入图像,为了提高准确率,使用二值图像。mode:定义轮廓检索模式,有四种可选模式: cv2.RETR_EXTERNAL:只检索最外面的轮廓; cv2.RETR_LIST:检索所有轮廓,并将其保存在列表中。 cv2.RETR_CCOMP:检索所有的轮廓,并将他们组织为两层:顶层是各部分的外部边界,第二层是空洞的边界; cv2.RETR_TREE:检索所有的轮廓,并重构嵌套轮廓的整个层次,我们常用这个method:定义轮廓逼近方法,我们这里主要使用下列两种方法 cv2.CHAIN_APPROX_NONE:存储所有的边界点。 cv2.CHAIN_APPROX_SIMPLE:仅存储水平、垂直和对角线方向的端点。下面我们来看一下具体的代码部分:将原始图片转换为二值图像这里为了方便演示,使用了一个非常规则的图片,包含几种基础的集合图形import cv2 def cv_show(img,name):    cv2.imshow(name,img) ​     # 第一步:转换为二值图 img = cv2.imread('my_contour.png') gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) cv_show(thresh,'contours') 轮廓检测调用cv2.findContours()函数binary, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE) binary:返回的就是二值图像contours:返回的是我们的轮廓信息hierarchy:返回的是层级结构绘制轮廓使用 cv2.drawContours() 函数可以将提取到的轮廓绘制在图像上,这样就可以可视化检测到的目标的形状和位置。主要包含下列几个参数draw_img:要在其上绘制轮廓的图像。contours:要绘制的轮廓列表。contourIdx:指定要绘制的轮廓的索引。使用 -1 表示绘制所有的轮廓。color:绘制轮廓的颜色,格式为 (B, G, R),其中 B、G 和 R 分别表示蓝色、绿色和红色的强度。例如 (255, 0, 0) 表示纯蓝色。thickness:轮廓线的粗细,默认值为 1。# 传入绘制图像,轮廓,轮廓索引,颜色模式,线条厚度 ​ draw_img = img.copy() #创建一个图像副本保存 res = cv2.drawContours(draw_img, contours, -1, (0, 0, 255), 2) #绘制所有轮廓 cv_show(res,'res') 上述我们绘制了所有的轮廓,有的时候我们只想得到其中的某一个轮廓,我们可以指定相应的轮廓索引,例如draw_img = img.copy() res = cv2.drawContours(draw_img, contours, 0, (255, 0, 0), 2)#绘制索引为0的轮廓 cv_show(res,'res') 2.轮廓特征在我们得到轮廓之后,如何利用这些信息呢,像其他图像特征一样,图像轮廓提供了许多与目标形状相关的特征信息,可以用于形状分析、目标检测和图像识别等任务。以下是一些常见的轮廓特征以及计算:面积(Area):轮廓所包围的区域的面积。 可以使用 cv2.contourArea(contour) 函数计算轮廓的面积。周长(Perimeter):轮廓的周长,即轮廓的闭合曲线的长度。 可以使用 cv2.arcLength(contour, closed) 函数计算轮廓的周长,其中 closed 参数指定轮廓是否为闭合曲线。边界框(Bounding Box):将轮廓包围在一个矩形框中。 可以使用 cv2.boundingRect(contour) 函数获取包围轮廓的最小矩形框的位置和大小。最小外接圆(Minimum Enclosing Circle):包围轮廓的最小半径的圆。 可以使用 cv2.minEnclosingCircle(contour) 函数获取包围轮廓的最小外接圆的圆心和半径。最小外接矩形(Minimum Enclosing Rectangle):包围轮廓的最小面积的矩形。 可以使用 cv2.minAreaRect(contour) 函数获取包围轮廓的最小外接矩形的位置、大小和旋转角度。凸包(Convex Hull):能够完全包围轮廓的凸多边形。 可以使用 cv2.convexHull(points) 函数获取轮廓的凸包。近似多边形(Approximation Polygon):用直线段逼近轮廓的多边形。 可以使用 cv2.approxPolyDP(curve, epsilon, closed) 函数对轮廓进行近似,其中 epsilon 是逼近精度参数。形心(Centroid):轮廓所包围区域的重心或平均位置。 可以使用 cv2.moments(contour) 函数计算轮廓的矩,然后通过计算质心来获取重心。这里我们以前两个为例演示如何使用,其他的特征计算都是直接调用相应的函数即可。 在上面我们已经得到了轮廓的信息contours,但是我们不能直接将其进行特征计算,因为它包含了所有的轮廓信息,因此我们要指定相应的索引再进行特征计算cnt = contours[0] #指定索引为0的轮廓 #面积 print(cv2.contourArea(cnt)) ​ #周长,True表示闭合的 print(cv2.arcLength(cnt,True)) 24578.0 586.4995617866516 3.轮廓近似轮廓近似是指用直线段或曲线段逼近实际轮廓的过程,从而减少轮廓中的点数并简化轮廓的表示。这可以帮助我们更有效地处理和分析轮廓。在 OpenCV 中,可以使用 cv2.approxPolyDP(curve, epsilon, closed) 函数来进行轮廓的近似。参数说明如下:curve:输入的轮廓。epsilon:表示近似精度的参数。较小的值会产生更准确的逼近,但会保留更多的细节和点。较大的值会导致更粗糙的逼近,但会减少点的数量。closed:一个布尔值,指示是否闭合轮廓。下面我们来看一个具体例子img = cv2.imread('contours2.png') ​ # 获取轮廓特征 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) binary, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE) cnt = contours[0] # 原始轮廓 draw_img = img.copy() original = cv2.drawContours(draw_img, [cnt], -1, (0, 0, 255), 2) ​ # 近似轮廓 epsilon = 0.1*cv2.arcLength(cnt,True) approx = cv2.approxPolyDP(cnt,epsilon,True) draw_img = img.copy() approx = cv2.drawContours(draw_img, [approx], -1, (0, 255, 0), 2) ​ res = np.hstack((original,approx)) cv_show(res,'res') ​ 左边是原始轮廓,右边是近似轮廓,可以看出对于轮廓变化较大的区域,近似效果有一定偏差,如果我们将epsilon设置的更小一点,如下所示此时近似效果非常接近。4.外接矩形很多时候,我们不是直接利用图像的轮廓进行操作,因为这些图像往往不规则,我们需要对其外接矩形或外接圆来进行分析,在opencv中,我们可以利用cv2.boundingRect()函数得到轮廓的外接矩形信息。img = cv2.imread('contours.png') gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) binary, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE) cnt = contours[0] x,y,w,h = cv2.boundingRect(cnt) img = cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2) cv_show(img,'img') 总结我们介绍了如何进行图形轮廓的检测、特征计算、轮廓近似处理。总的来说,图像轮廓具有以下特点:轮廓是由连续的曲线或边界组成的,能够准确地描述目标的形状。轮廓可以用于目标的识别、分类和形状分析。轮廓可以通过计算形状的特征(如面积、周长、重心等)来获取更多的信息。可以使用图像轮廓进行图像分割、边缘检测和图像增强等任务。
0
0
0
浏览量825
乌兰推

【Opencv入门到项目实战】(十一):OCR文档识别

1.引言今天我们来看一个OCR相关的文档扫描项目。首先我们先来介绍一些相关理论1.1 什么是光学字符识别 (OCR)OCR(即光学字符识别)是识别图像中的文本并将其转换为电子形式的过程。这些图像可以是手写文本、打印文本(如文档、收据、名片等),甚至是自然场景照片。简单来说,OCR 有两个部分。第一部分是文本检测,确定图像内的文本部分。第二部分文本识别,从图像中提取文本。 结合使用这些技术可以从任何图像中提取文本。具体的流程如下图所示OCR 在各个行业都有广泛的应用(主要目的是减少人工操作)。它已经融入我们的日常生活,并且有很多的应用。1.2 应用领域OCR 越来越多地被各行业用于数字化,以减少人工工作量。这使得从商业文档、收据、发票、护照等中提取和存储信息变得非常容易和高效,几十年前,OCR 系统的构建非常昂贵且繁琐。但计算机视觉和深度学习领域的进步使得我们现在自己就可以构建一个OCR 系统。但构建 OCR 系统需要利用到我们之前介绍的一系列方法。2.项目背景介绍背景:我们有一张随手拍的发票照片如下,我们希望识别出文档信息并扫描思考:我们如何实现上述需求呢?思考:我们如何实现上述需求呢?首先,我们的算法应该能够正确的对齐文档,检测图像的边界,获得目标文本图像其次,我们能对目标文本图像的文档进行扫描下面我们来看一下具体如何在Opencv中处理这里一共需要四大步第一步,边缘检测,第二步,提取轮廓。第三步,透视变换,使得图像对齐,从上图可以看出,我们的图片是一个倾斜的,我们需要通过各种转换方法将其放平。第四步,OCR识别3.边缘检测3.1 原始图像读取首先,我们读取要扫描的图像。下述代码我们计算了一个ratio比例,这是因为我们后续要对图像进行resize操作,里面每一个点的坐标也会有相同的一个变化,因此,我们先算出来这样一个比例,可以推导出resize完之后图像的坐标变化,然后方便我们后续在原图上进行修改。# 读取输入 image = cv2.imread('images/receipt.jpg') #坐标也会相同变化 ratio = image.shape[0] / 500.0 #这里我们首先得到一个比例,方便后续操作 orig = image.copy() 3.2 预处理下面我们对图形进行一些基本的预处理工作,包含resize、灰度处理、二值处理。第一,我们定义了一个resize()函数,它的基本逻辑是根据输入的高度或宽度,自动的计算出宽度或高度。第二,我们将图形进行灰度处理第三,我们使用gaussian滤波器去除噪音点​ def resize(image, width=None, height=None, inter=cv2.INTER_AREA): dim = None (h, w) = image.shape[:2] if width is None and height is None: return image if width is None: r = height / float(h) dim = (int(w * r), height) else: r = width / float(w) dim = (width, int(h * r)) resized = cv2.resize(image, dim, interpolation=inter) return resized image = resize(orig, height = 500) # ​ # 预处理 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 灰度处理 gray = cv2.GaussianBlur(gray, (5, 5), 0) # 高斯滤波器 ​ ​ 3.3 结果展示经过上述得到预处理后的图片,经过canny边缘检测。# 展示预处理结果 edged = cv2.Canny(gray, 75, 200) # canny边缘检测,得到边缘 ​ print("STEP 1: 边缘检测") cv2.imshow("Image", image) #原始图像 cv2.imshow("Edged", edged) #边缘结果 cv2.waitKey(0) cv2.destroyAllWindows() 现在我们得到边缘检测的结果,可以看到有很多个边缘,我们做文档扫描,需要的是最外面的结果,接下来我们来具体如何实现。3.轮廓检测我们先来思考一下最外面这个轮廓它有什么特点。首先,它是最大的,因此,我们可以根据它的面积或者周长进行排序,这里我们对面积进行排序。然后我们要去找轮廓,这里我们遍历每一个轮廓,然后去计算轮廓的一个近似,因为直接算轮廓的时候不太好算,往往是一个不规则形状,我们做一个矩形近似,然后此时就只需要确定四个点就行。具体代码如下: # 轮廓检测 cnts = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)[1] cnts = sorted(cnts, key = cv2.contourArea, reverse = True)[:5] #按照面积排序 ​ # 遍历轮廓 for c in cnts: # 计算轮廓近似 peri = cv2.arcLength(c, True) # C表示输入的点集 # epsilon表示从原始轮廓到近似轮廓的最大距离,它是一个准确度参数 # True表示封闭的 approx = cv2.approxPolyDP(c, 0.02 * peri, True) #轮廓近似 ​ # 4个点的时候就拿出来 if len(approx) == 4: screenCnt = approx break ​ # 展示结果 print("STEP 2: 获取轮廓") cv2.drawContours(image, [screenCnt], -1, (0, 255, 0), 2) cv2.imshow("Outline", image) cv2.waitKey(0) cv2.destroyAllWindows() ​ 4.透视变换透视变换(Perspective Transformation) ,也称为投影变换,它可以用于纠正图像畸变、实现视角变换和图像合成等应用。借助透视变换,我们可以从不同视角获得准确的图像数据,并进行更精确的分析、处理和识别。它的基本原理是基于相机的投影模型,通过处理图像中的四个控制点,将原始图像上的任意四边形区域映射到新的位置和形状上,我们需要得到四个输入坐标和四个输出坐标。通常情况下,透视变换会改变图像中的视角、缩放和旋转等属性。它有几个关键步骤如下:控制点选择:为了进行透视变换,我们需要选择原始图像中的四个控制点(例如四个角点),以定义目标区域的形状和位置。这些控制点应该在原始图像和目标图像之间有明确的对应关系,然后通过高度和宽度信息,我们计算出目标图像的四个控制点透视变换矩阵:通过使用控制点的坐标,可以计算出透视变换矩阵,透视变换矩阵是一个3x3的矩阵。它包含了图像变换所需的所有信息。这里需要输入坐标和输出坐标,然后利用cv2.getPerspectiveTransform函数获取变换矩阵。通过将透视变换矩阵应用于原始图像上的点,可以得到它们在目标图像中的对应位置。接下来我们来看一下具体是如何实现的,首先我们定义了一个ordr_points()函数来获取坐标点,然后我们定义four_point_transform函数来实现透视变换。具体代码如下,# 获取坐标点 def order_points(pts): # 一共4个坐标点 rect = np.zeros((4, 2), dtype = "float32") ​ # 按顺序找到对应坐标0123分别是 左上,右上,右下,左下 # 计算左上,右下 s = pts.sum(axis = 1) rect[0] = pts[np.argmin(s)] rect[2] = pts[np.argmax(s)] ​ # 计算右上和左下 diff = np.diff(pts, axis = 1) rect[1] = pts[np.argmin(diff)] rect[3] = pts[np.argmax(diff)] ​ return rect ​ def four_point_transform(image, pts): # 获取输入坐标点 rect = order_points(pts) (tl, tr, br, bl) = rect ​ # 计算输入的w和h值 widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2)) widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2)) maxWidth = max(int(widthA), int(widthB)) ​ heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2)) heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2)) maxHeight = max(int(heightA), int(heightB)) ​ # 变换后对应坐标位置 dst = np.array([ [0, 0], [maxWidth - 1, 0], [maxWidth - 1, maxHeight - 1], [0, maxHeight - 1]], dtype = "float32") ​ # 计算变换矩阵 M = cv2.getPerspectiveTransform(rect, dst) #通过输入和输出坐标,可以计算出M矩阵 warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight)) ​ # 返回变换后结果 return warped ​ ​ 定义好上述函数之后,接下来看一下经过同时变换之后的结果,为了方便展示,我们再进行二值化处理​ # 透视变换 warped = four_point_transform(orig, screenCnt.reshape(4, 2) * ratio) #这里乘ratio是为了恢复我们原始图像坐标 ​ # 二值处理 warped = cv2.cvtColor(warped, cv2.COLOR_BGR2GRAY) ref = cv2.threshold(warped, 100, 255, cv2.THRESH_BINARY)[1] cv2.imwrite('scan.jpg', ref) # 展示结果 print("STEP 3: 变换") cv2.imshow("Scanned", resize(ref, height = 650)) cv2.waitKey(0) cv2.destroyAllWindows() 可以看到我们现在就得到了扫描之后得到的结果,并且我们保存为scan.jpg操作5.OCR识别得到扫描后的文档之后,我们需要对其中的字符进行识别,这里我们要用到tesseract工具包,我们先来看一下如何安装相关环境。5.1 tesseract安装安装地址:digi.bib.uni-mannheim.de/tesseract/首先选择一个合适的版本进行安装就行,我这里选择最新的w64版本,如何安装时一直点击下一步就行,但是我们要记住安装的路径。注意:我们需要进行环境变量配置把刚刚安装的路径添加到环境变量中即可接下来我们希望在python中使用它,因此要下载对应的python工具包。安装命令如下:pip install pytesseract5.2 字符识别我们刚刚已经得到了扫描后的图像,并保存为scan.jpg如下所示接下来我们希望把其中的文本字符全部提取出来,我们来看一下具体代码吧from PIL import Image import pytesseract import cv2 import os ​ # 读取图片 image = cv2.imread('scan.jpg') ​ # 灰度处理 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) ​ # 二值处理 gray = cv2.threshold(gray, 0, 255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]     ​ filename = "{}.png".format(os.getpid()) cv2.imwrite(filename, gray) # OCR识别,提取字符     text = pytesseract.image_to_string(Image.open(filename)) print(text) ​ x * KK KK K KR KH KR RK KK ​ WHOLE FOODS TM AR K CE T) ​ WHOLE FOODS MARKET ~ WESTPORT, CT 06880 399 POST RD WEST - (203) 227-6858 ​ 365 BACUN LS NP 499 $65 BACON LS NP 4.99 ​ 365 BACON LS NP 4.99 ​ 365 BACON LS NP 4.99 BROTH CHTC NP 2.19 ​ FLOUR ALMUND NP 11.99 ​ CHKN BRST BNLSS SK NP 18.80 HEAVY CREAM NP 3.39 ​ BALSMC REDUCT NP 6.49 ​ BEEF GRND 85/15 NP 5.04 JUICE COF CASHEW L NP = 8.99 DOCS PINT ORGANIC NF 14.49 HNY ALMOND BUTTER NP 9.99 xeee TAX = 00 9 BAL 101.33 ​ TTA AATDA ABH HH oy 对比一下可以看到识别的字符都比较准确。
0
0
0
浏览量2015
乌兰推

【Opencv入门到项目实战】(十二):答题卡识别项目实战

1.项目背景今天我们来看一下怎么样来进行一个答题卡自动识别的功能。首先我们来看一下答题卡长什么样子,大家参加各项考试应该都很熟悉了,我们要把自己的答案涂写在对应选项中,如下图所示我们可以看到这里一共有5个选项,然后填充了其中一个选项,以第一题为例,此时我们涂写了b,它就是实心的,那么它和A、C、D、E就不太一样了,因此首先我们要做的就是利用图像学的一些方法将其识别出来;识别出来之后我们跟正确答案进行对比;最后我们再进行一个评分功能,对每个选项进行识别,判断正确率及其得分。在整个过程中,最核心的是识别出填写的答案要实现这个目的,我们需要进行下列一些工作,图像滤波操作、边缘检测、透视变换、二值处理、掩码等等之前介绍过的图像学知识,最后判断每道题选择的答案,然后我们再去对比正确答案来进行自动评分。我们来看一下结果示例:其中黑色圈出来的是标准答案。下面我们来看具体代码细节2.预处理首先我们定义后续相关要用到的函数,这些函数在具体用到时会详细解读:2.1 定义相关参数和函数import numpy as np import imutils import cv2 ​ # 正确答案 ANSWER_KEY = {0: 1, 1: 4, 2: 0, 3: 3, 4: 1} ​ ​ ​ # 展示图像 def cv_show(name,img):        cv2.imshow(name, img)        cv2.waitKey(0)        cv2.destroyAllWindows()   2.2 高斯滤波和边缘检测代码如下:# 图片读取与复制 image = cv2.imread(path) contours_img = image.copy() ​ # 灰度图 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) ​ # 高斯滤波 blurred = cv2.GaussianBlur(gray, (5, 5), 0) cv_show('blurred',blurred) ​ # 边缘检测 edged = cv2.Canny(blurred, 75, 200) cv_show('edged',edged) 上述代码要实现的功能:首先我们将图片读取进来,然后将图形copy来进行后续操作第一步,然后将图像转换成灰度图第二步,高斯滤波,去除周围噪音点,得到结果如下第三步,边缘检测,这里我们直接使用canny边缘检测,处理比较简单,我们直接看结果如下从图中可以看到此时我们基本得到了图片的边界,接下来我们要对这个图片进行一个透视变换,使得其显示的方方正正。因此首先我们要得到它最外围的轮廓,因此接下来我们需要做一个轮廓检测2.3 轮廓检测代码如下:# 轮廓检测 cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1]  # 轮廓检测三个返回值,只提取轮廓值 cv2.drawContours(contours_img,cnts,-1,(0,0,255),3)  #绘制轮廓 cv_show('contours_img',contours_img) docCnt = None ​ ​ 此时红色框框就是得到的最外围的轮廓,但是其仍然不是方方正正的,因此我们需要进行转换,首先我们要得到4个坐标,接下来对轮廓面积进行排序,然后遍历对轮廓进行近似,得到一个多边形,当多边形长度为4,就结束遍历if len(cnts) > 0: # 根据轮廓大小进行排序 cnts = sorted(cnts, key=cv2.contourArea, reverse=True) ​ # 遍历每一个轮廓 for c in cnts: # 近似 peri = cv2.arcLength(c, True) approx = cv2.approxPolyDP(c, 0.02 * peri, True) ​ # 准备做透视变换 if len(approx) == 4: docCnt = approx break 接下来要做我们之前介绍的透视变换2.4 透视变换首先我们要拿到原始的四个坐标点,然后分别算​,得到变换后的坐标计算变换矩阵M根据变换矩阵M,此时输入一张图像,可以直接得到变换后的图像具体代码如下# 执行透视变换 ​ # 获取坐标点 def order_points(pts): # 一共4个坐标点 rect = np.zeros((4, 2), dtype = "float32") ​ # 按顺序找到对应坐标0123分别是 左上,右上,右下,左下 # 计算左上,右下 s = pts.sum(axis = 1) rect[0] = pts[np.argmin(s)] rect[2] = pts[np.argmax(s)] ​ # 计算右上和左下 diff = np.diff(pts, axis = 1) rect[1] = pts[np.argmin(diff)] rect[3] = pts[np.argmax(diff)] ​ return rect ​ # 坐标转换 def four_point_transform(image, pts): # 获取输入坐标点 rect = order_points(pts) (tl, tr, br, bl) = rect ​ # 计算输入的w和h值 widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2)) widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2)) maxWidth = max(int(widthA), int(widthB)) ​ heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2)) heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2)) maxHeight = max(int(heightA), int(heightB)) ​ # 变换后对应坐标位置 dst = np.array([ [0, 0], [maxWidth - 1, 0], [maxWidth - 1, maxHeight - 1], [0, maxHeight - 1]], dtype = "float32") ​ # 计算变换矩阵 M = cv2.getPerspectiveTransform(rect, dst) warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight)) ​ # 返回变换后结果 return warped ​ ​ warped = four_point_transform(gray, docCnt.reshape(4, 2)) cv_show('warped',warped) 可以看到经过透视变换之后的结果变得非常规整。现在我们预处理工作基本完成3. 答案识别接下来我们要检测这些答案,一般我们是用铅笔填充,其他地方空出来了,此时相当于有两种主体,一种是没被填充的,一种是填充的,因此在这里我们可以进行一个二值处理,3.1 二值处理在这里我们使用Otsu's阈值处理,让其自适应确定阈值# Otsu's 阈值处理 thresh = cv2.threshold(warped, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1] cv_show('thresh',thresh) thresh_Contours = thresh.copy() ​ 可以看到,此时填充答案的地方是特别亮的,接下来我们要让计算机去识别出这些答案。3.2 圆圈轮廓检测因为在涂答题卡的时候有的时候绘图到外面去,因此我们还需要再进行一个轮廓检测,来找到每个圆圈的轮廓结果# 找到每一个圆圈轮廓 cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1] cv2.drawContours(thresh_Contours,cnts,-1,(0,0,255),3) cv_show('thresh_Contours',thresh_Contours) questionCnts = [] 接下来我们在得到轮廓的一个外接矩形,以适应涂取误差。3.3 判断结果# 遍历得到外接矩形 for c in cnts: # 计算比例和大小 (x, y, w, h) = cv2.boundingRect(c) ar = w / float(h) ​ # 根据实际情况指定标准 if w >= 20 and h >= 20 and ar >= 0.9 and ar <= 1.1: questionCnts.append(c                             # 对检测结果进行排序                             def sort_contours(cnts, method="left-to-right"):    reverse = False    i = 0    if method == "right-to-left" or method == "bottom-to-top":        reverse = True    if method == "top-to-bottom" or method == "bottom-to-top":        i = 1    boundingBoxes = [cv2.boundingRect(c) for c in cnts]   (cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes),                                        key=lambda b: b[1][i], reverse=reverse))    return cnts, boundingBoxes ​ # 按照从上到下进行排序 questionCnts = sort_contours(questionCnts, method="top-to-bottom")[0] correct = 0 ​ # 每排有5个选项 for (q, i) in enumerate(np.arange(0, len(questionCnts), 5)): # 排序 cnts = sort_contours(questionCnts[i:i + 5])[0] bubbled = None ​ # 遍历每一个结果 for (j, c) in enumerate(cnts): # 使用mask来判断结果,每一行进行比较 mask = np.zeros(thresh.shape, dtype="uint8") cv2.drawContours(mask, [c], -1, 255, -1) #-1表示填充 cv_show('mask',mask) # 通过计算非零点数量来算是否选择这个答案 mask = cv2.bitwise_and(thresh, thresh, mask=mask) total = cv2.countNonZero(mask) ​ # 通过阈值判断 if bubbled is None or total > bubbled[0]: bubbled = (total, j) ​ # 对比正确答案 color = (0, 0, 255) k = ANSWER_KEY[q] ​ # 判断正确 if k == bubbled[1]: color = (0, 255, 0) correct += 1 ​ # 绘图 # cv2.drawContours(warped, [cnts[k]], -1, color, 3) ​ 3.4 评分结果有了上述结果,我们现在统计评分情况如下score = (correct / 5.0) * 100 print("score: {:.2f}%".format(score)) cv2.putText(warped, "{:.2f}%".format(score), (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2) #cv2.imshow("Original", image) cv2.imshow("Exam", warped) cv2.waitKey(0) cv2.destroyAllWindows() 可以看到第一份正确率为80%第二分评分结果:第三份评分结果:
0
0
0
浏览量1501
乌兰推

【Opencv入门到项目实战】(一):图形基本操作

0.Opencv环境配置这一环节我们主要介绍Opencv图形处理框架,首先我们要配置好对应的环境。大家在网上可以搜到各种各样的教程,这里我的环境如下python3.6, opencv-contrib-python3.4.1.15,opencv-python3.4.1.15大家可以根据自己的python环境自习安装对应的版本。以我的环境为例,安装命令如下pip install opencv-python==3.4.1.15pip install opencv-contrib-python==3.4.1.151.图像读取接下来我们来看在opencv当中,图像最基本的操作。首先,我们得先知道计算机是如何看一张图像的我们来观察这个图,我们把它叫做丽娜,我们把图片分成了很多很多小方格,然后我们拿出其中的一个方格观察一下。在这个方格当中,我们的每一个就是一个大区域,它是有很多个小块儿所组成的,其中的每一个小格,它叫做一个像素点,计算机当就是由这些像素点来构成一张图像的,那像素点又是什么呢?其实,它就是一个值,我们来看最右边,每一个矩阵里边儿的组成,各种值,例如81,116,…,133,170。它们就是构成像素点的每一个值了,而这个数值大小意味着什么呢?在计算机当中,每一个像素点的值是在0-255之间进行浮动的,表示该点亮度,0表示很亮,255表示很暗然后我们再来看R、G、B三个通道,每一个彩色图都是由RGB三颜色通道组成的,然后每一个矩阵分别对应每一个通道的亮度。对于灰度图而言,就只有一个通道来表示亮度。这些像素点组成了一个矩阵,这个矩阵就表示图像的大小,例如我们假设矩阵是300×300,那相应的rgb三个通道都是300×300,因此整个图形的维度就是(300,300,3)1.1彩色图像读取当我们要分析一张图片时,首先就是读取它转换成一个矩阵的格式,下面我们正式看一下如何读取图片import cv2 #opencv读取的格式是BGR import matplotlib.pyplot as plt import numpy as np %matplotlib inline img=cv2.imread('yangqi.jpg') 在这里我们先导入cv2,然后找到你要读取的图片地址,我这里用的是我们家小狗的图片,他的名字叫洋气,‘yangqi.jpg’,使用cv.imread()函数就会把这张图像读进来。我们来看一下它的结果是什么img array([[[ 99, 141, 148], [ 95, 137, 144], [ 78, 120, 127], ..., [ 69, 88, 103], [ 81, 100, 113], [ 88, 107, 120]], [[ 93, 137, 144], [ 49, 93, 100], [ 86, 130, 137], ..., [175, 197, 209], [195, 217, 229], [200, 222, 234]], [[ 30, 76, 84], [ 23, 66, 75], [ 72, 115, 124], ..., [ 43, 65, 77], [ 42, 64, 76], [ 43, 65, 77]], ..., [[178, 209, 232], [158, 189, 212], [154, 185, 206], ..., [ 68, 78, 85], [ 54, 64, 71], [ 83, 93, 100]], [[164, 195, 216], [138, 169, 190], [168, 197, 218], ..., [ 84, 94, 101], [ 92, 102, 109], [ 87, 97, 104]], [[167, 196, 217], [129, 158, 179], [102, 131, 152], ..., [101, 111, 118], [124, 134, 141], [ 96, 106, 113]]], dtype=uint8) img.shape (238, 218, 3) 可以看到,它返回的是一个ndarray的结构,纬度为(238, 218, 3),因为我们这里是一张彩色图片,所以有三个通道。现在我们已经把图像数据给读进来了,有些时候就是随着我们对图像进行处理,例如边缘检测,或者是一些更复杂的操作。在做操作过程当中,我们想观察一下,这个图像它变换成什么样子了,因此我们需要展示图像,我们可以用matplotlib或者cv2,但是需要注意的是openCV默认读取是BGR的格式,所以在读取的时候,如果我们要用matplotlib的话需要进行相应的转变。我们这里全部都用openCV自带的函数来展示。这里我们利用cv.imshow()函数,传入两个参数,第一个参数表示窗口的名称,第二个函数就是我们要读取的图像数据,也就是我们刚刚得到的那个矩阵#图像展示 cv2.imshow('cat',img) cv2.waitKey(0) # 等待时间,0表示按任意键结束 cv2.destroyAllWindows() 为了方便使用,我们定义函数cv_show(),以后就直接调用这个函数来进行图像读取def cv_show(name,img): cv2.imshow(name,img) cv2.waitKey(0) cv2.destroyAllWindows() 1.2 灰色图像读取在opencv中,默认都是进行彩色图像读取,有的时候我们想要读取灰度图,在这里我们指定cv2.IMREAD_GRAYSCALEimg=cv2.imread('yangqi.jpg',cv2.IMREAD_GRAYSCALE) img array([[138, 134, 117, ..., 90, 102, 109], [134, 90, 127, ..., 198, 218, 223], [ 73, 64, 113, ..., 66, 65, 66], ..., [212, 192, 188, ..., 79, 65, 94], [198, 172, 200, ..., 95, 103, 98], [199, 161, 134, ..., 112, 135, 107]], dtype=uint8) img.shape (238, 218) 可以看到现在的数据变成了一个二维矩阵了,此时是一个单通道,我们再次读取cv2.imshow('cat2',img) cv2.waitKey(0) cv2.destroyAllWindows() 可以看出现在这个图片就变成灰白了2.视频读取视频读取其实和我们图像读取类似,我们先把视频进行拆分,拆分成其中的每一帧,然后基于每一帧图像去做。下面我们具体来看一下如何使用opencv读取视频,首先找到你要读取的视频路径,然后调用cv2.VideoCapture()函数得到视频流,然后我们使用isOpened()来判断是否能打开,如果能打开,使用vc.read()一帧一帧的读取视频信息,然后返回两个值,open为布尔值,frame为读取的每一帧的图像数据vc = cv2.VideoCapture('test.mp4') # 检查是否打开正确 if vc.isOpened(): oepn, frame = vc.read() else: open = False while open: ret, frame = vc.read() if frame is None: break if ret == True: gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) #转换成黑白图 cv2.imshow('result', gray)#显示每一帧图像 if cv2.waitKey(100) & 0xFF == 27:#等待时间 break vc.release() cv2.destroyAllWindows()3.ROI读取3.1 图形切片处理接下来我们来看,当我拿到一张图像时,有的时候我只对某一个区域感兴趣,例如我们刚刚的cat图片,我可能想要观察的是中间的某一个特定区域,这就是region of interest(ROI)读取,如下所示,我们对图形进行切片处理,得到它的左上部分结果。img=cv2.imread('yangqi.jpg') dog=img[0:50,0:200] cv_show('yangqi',dog) 3.2 提取颜色通道有的时候我们需要对图像进行一些特殊的分析,假设一个彩色图像,它是由三个颜色通道组成,BGR(Opencv默认顺序)。在openCV当中,我们可以使用cv2.split()把三个通道分别提取出来看一下b,g,r=cv2.split(img) 这样我们得到了每一个通道的数据r.shape,b.shape,g.shape ((238, 218), (238, 218), (238, 218)) 可以看到每一个通道都是一个二维矩阵。在我们切片完处理之后,我们希望再将其组合在一起,我们可以直接调用cv2.merge(),注意我们的顺序是BGRimg=cv2.merge((b,g,r))#顺序为bgr img.shape 我们前面一直说彩色图片有三个通道,那么每个通道的图像是怎样的呢?我们接下来分别来看看。首先,我们只看R通道的图像,那么我们设置B,G通道对于的像素点值全为0tmp_img = img.copy() tmp_img[:,:,0] = 0#第一个通道B全部设为0 tmp_img[:,:,1] = 0#第二个通道G全部设为0 cv_show('Red',tmp_img) 可以看到得到了一张红色图片,下面类似的操作可以分别得到蓝色和绿色的结果tmp_img = img.copy() tmp_img[:,:,0] = 0 tmp_img[:,:,2] = 0 cv_show('Green',tmp_img) tmp_img = img.copy() tmp_img[:,:,1] = 0 tmp_img[:,:,2] = 0 cv_show('Blue',tmp_img) 4.图像填充接下来我们讨论一下图像填充,这个也比较常见,我们之间在介绍卷积的时候说了padding,它就是在图像在边界进行填充。下面我们就介绍一下使用Opencv来填充图像的一些方法top_size,bottom_size,left_size,right_size = (100,100,100,100)#要填充的大小 replicate = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, borderType=cv2.BORDER_REPLICATE) #复制法 reflect = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size,cv2.BORDER_REFLECT) #反射法 reflect101 = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, cv2.BORDER_REFLECT_101) #反射法101 wrap = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, cv2.BORDER_WRAP) #外包装法 constant = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size,cv2.BORDER_CONSTANT, value=0) #常数填充,0表示黑色 BORDER_REPLICATE:复制法,直接复制最边缘的像素BORDER_REFLECT:反射法,对感兴趣的图像中的像素在两边进行复制,例如,原始为1234,那么我们复制的方法为321|1234|432BORDER_REFLECT_101:反射法101,以最边缘像素为轴进行对称复制,例如,原始为1234,那么我们复制的方法为432|1234|321BORDER_WRAP:外包装法,例如原始为1234,复制为234|1234|123BORDER_CONSTANT:常量法,常数值填充。查看结果import matplotlib.pyplot as plt plt.subplot(231), plt.imshow(img, 'gray'), plt.title('ORIGINAL') plt.subplot(232), plt.imshow(replicate, 'gray'), plt.title('REPLICATE') plt.subplot(233), plt.imshow(reflect, 'gray'), plt.title('REFLECT') plt.subplot(234), plt.imshow(reflect101, 'gray'), plt.title('REFLECT_101') plt.subplot(235), plt.imshow(wrap, 'gray'), plt.title('WRAP') plt.subplot(236), plt.imshow(constant, 'gray'), plt.title('CONSTANT') plt.show() 5.数值运算与图像融合接下来我们来看一下在Opencv中的基本数值运算,我们先读取两张图像,yangqi和另一只猫的图片catimg_dog=cv2.imread('yangqi.jpg') img_cat=cv2.imread('cat.jpg') 5.1加法运算img_cat2= img_cat +10 img_cat[:5,:,0] array([[142, 146, 151, ..., 156, 155, 154], [107, 112, 117, ..., 155, 154, 153], [108, 112, 118, ..., 154, 153, 152], [139, 143, 148, ..., 156, 155, 154], [153, 158, 163, ..., 160, 159, 158]], dtype=uint8) 然后我们对cat加10,这相当于在每个像素点都加10.img_cat2[:5,:,0] array([[152, 156, 161, ..., 166, 165, 164], [117, 122, 127, ..., 165, 164, 163], [118, 122, 128, ..., 164, 163, 162], [149, 153, 158, ..., 166, 165, 164], [163, 168, 173, ..., 170, 169, 168]], dtype=uint8) 在opencv中,我们有两种方法实现两个矩阵相加# 方法一 (img_cat + img_cat2)[:5,:,0] array([[ 38, 46, 56, ..., 66, 64, 62], [224, 234, 244, ..., 64, 62, 60], [226, 234, 246, ..., 62, 60, 58], [ 32, 40, 50, ..., 66, 64, 62], [ 60, 70, 80, ..., 74, 72, 70]], dtype=uint8) # 方法二 cv2.add(img_cat,img_cat2)[:5,:,0] array([[255, 255, 255, ..., 255, 255, 255], [224, 234, 244, ..., 255, 255, 255], [226, 234, 246, ..., 255, 255, 255], [255, 255, 255, ..., 255, 255, 255], [255, 255, 255, ..., 255, 255, 255]], dtype=uint8) 方法一:直接如上所示,用a+b,这样得出来的结果大家可以看第一个为38,我们知道刚刚两个矩阵的第一个元素相加为142+152=294,之前我们说每一个像素的范围为0-255,在这里超过了255,因此我们这里实际上是294%256=38,进行了一个取余数的操作方法二:cv2.add(),这种方法会将超过255的值定义为2555.2 图像融合首先我们来看一下两张图片的维度img_cat.shape,img_dog.shape ((414, 500, 3), (238, 218, 3)) 由于维度不同,不能直接相加,这里我们需要使用cv.resize将dog的图片维度转换,这在我们之前CNN中也用过类似的操作。img_dog = cv2.resize(img_dog, (500, 414)) img_dog.shape (414, 500, 3) 现在两张图片的纬度一样了,我们可以调用cv2.addWeighted()将两张图片融合。这里有5个参数,分别是图片1数据、图片1权重、图片2数据、图片2权重、常数项。具体操作就是0.4*cat+0.6*dog+0res = cv2.addWeighted(img_cat, 0.4, img_dog, 0.6, 0) 可以看到这个结果当中,左边像猫,右边像狗,这样就好像两张图像就融合在一起了。6. 总结我们介绍了Opencv环境配置、基本的图像和视频读取、读取感兴趣的部分图像、图像填充、以及在Opencv中的基本数值计算和图像融合。
0
0
0
浏览量2034
乌兰推

【Opencv入门到项目实战】(九):harris角点检测|SIFT|特征匹配

1.harris角点检测这一节我们来讨论一下Harris角点检测,由Chris Harris和Mike Stephens于1988年提出。该算法通过计算图像的局部灰度变化来检测角点,并利用协方差矩阵的特征值来确定是否为角点。它的基本思想是,对于图像I(x,y),当在点(x,y)处平移(​x,​y)后的灰度变化有多大。我们定义其变换后的自相似性为:w(x, y)是以点(x,y)为中心的一个窗口,一般我们用高斯窗口函数,用于平滑梯度。然后对右边利用泰勒展开:其中,Ix和Iy分别表示图像在x和y方向上的梯度代入后近似得到下列结果:其中M为协方差矩阵:接下来我们利用协方差的特征值来计算每个像素点的角点相响应值R其中,det(M)表示协方差矩阵M的行列式,trace(M)表示协方差矩阵M的迹,​是一个常数,用于调整响应函数的灵敏度。在Opencv中,我们调用cv2.cornerHarris()函数实现import cv2 import numpy as np ​ # 导入图片 img = cv2.imread('test_1.jpg') print ('img.shape:',img.shape) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) gray = np.float32(gray)#转换为float32格式 dst = cv2.cornerHarris(gray, 2, 3, 0.04) print ('dst.shape:',dst.shape) ​ img.shape: (512, 512, 3) dst.shape: (512, 512) img[dst>0.01*dst.max()]=[0,0,255] #非极大值抑制,将边界点标红 cv2.imshow('dst',img) cv2.waitKey(0) cv2.destroyAllWindows() 上图可以看出,我们将当中的角点都检测出来了Harris角点检测算法的优点在于它对旋转、尺度和亮度的变化具有一定的鲁棒性,并且适用于不同类型的图像。该算法还相对简单,计算速度较快。然而,Harris角点检测算法也存在一些限制。例如,它对侧向边缘非常敏感,会将边缘点误判为角点。此外,该算法对于纹理较为平滑的区域可能无法准确地检测到角点。2.尺度不变特征变换(SIFT)Scale Invariant Feature Transform(SIFT) 是一种用于计算机视觉和图像处理中的特征检测和描述的算法,可以说是在计算机视觉一种非常流行和广泛使用的算法,它能够检测和描述图像中的关键点,并具有尺度、旋转和视角的不变性,适用于各种图像处理任务,例如目标识别、图像匹配、图像拼接等。SIFT算法主要包括:尺度空间检测、特征关键点检测、特征描述等,接下来我们来分别看一下这些概念以下是对SIFT算法的详细介绍:特征点检测: 尺度空间极值检测:SIFT首先在不同尺度下使用高斯模糊滤波器构建尺度空间金字塔,然后通过比较每个像素与其周围像素及所处尺度上的像素进行检测,找到图像中的关键点。 关键点精化:通过利用尺度空间的极值点,对关键点进行精化,计算其精确位置和尺度尺度,并根据梯度方向来确定主方向。特征描述: 局部图像描述子:以关键点为中心,在其周围的邻域内构建一个具有旋转不变性和尺度不变性的局部图像描述子。该描述子基于关键点附近的梯度方向和强度信息,通过生成一个特征向量来描述关键点周围的图像结构。 方向直方图:在计算局部图像描述子之前,首先根据关键点周围的梯度方向计算一个方向直方图,用来确定主方向。特征匹配: 特征向量匹配:将两幅图像的特征向量进行匹配,通常使用最近邻匹配方法(如欧氏距离)来寻找每个特征点的最佳匹配。同时,还可以使用二次最近邻比率测试来过滤不可靠的匹配。 鲁棒性和一致性:为了提高匹配的鲁棒性和一致性,可以使用诸如RANSAC(随机抽样一致性)等方法来剔除异常和错误的匹配点。SIFT算法的关键优势在于其对尺度、旋转和视角的不变性,使得它能够在多种条件下鲁棒地检测和匹配图像中的特征点。此外,SIFT算法还具有以下特点:独特性:SIFT特征是在图像中独一无二的,它们可以用来区分不同的图像区域。不变性:SIFT特征对于有限的视角变化、尺度变化以及轻微的仿射变换都具有较好的不变性。高鲁棒性:SIFT算法对于光照变化、噪声和部分遮挡等情况下仍能产生准确的匹配结果。可扩展性:SIFT算法可以应用于各种图像尺寸和分辨率的图像,并且在大规模图像数据库中进行高效匹配。,因此在目标识别、图像匹配和图像拼接等应用中具有重要的作用。2.1图像尺度空间我们知道在一定的范围内,无论物体是大还是小,我们人眼都可以分辨出来,然而计算机要有相同的能力却很难,所以要让机器能够对物体在不同尺度下有一个统一的认知,就需要考虑图像在不同的尺度下都存在的特点。图像尺度空间是指在不同尺度下对图像进行分析和处理的一种表示方式。在图像尺度空间中,同一物体或结构的特征在不同尺度下具有不同的尺度信息。这是因为图像中的物体和结构可能以不同的尺度出现,例如大小、边缘和纹理等。通过在不同尺度下分析图像,我们可以获取更全面和鲁棒的特征表示,以适应不同尺度上的目标检测、识别和描述任务。尺度空间的获取通常使用高斯模糊来实现,高斯金字塔: 高斯金字塔是一种通过对图像进行连续的高斯滤波和下采样操作来构建图像尺度空间的方法。具体步骤如下: 先对原始图像应用一个初始尺度的高斯滤波器。 对滤波后的图像进行下采样(通常是进行二次减半),得到下一层金字塔图像。 重复以上步骤,直到达到预定的尺度层数。多分辨率金字塔,它是通过将原始图像进行分解,得到一系列具有不同分辨率的图像层级,从而实现对图像的多尺度分析和处理通常采用逐步降采样(downsampling)和上采样(upsampling)的方法。 首先,原始图像通过降采样操作被缩小为较低分辨率的图像,然后这个较低分辨率的图像再次进行降采样,直到达到所需的分辨率层级。 这样就形成了一个金字塔状的层级结构,其中每个层级的图像都比上一层级的图像具有更低的分辨率。 在每层进行高斯滤波器操作 多分辨率金字塔的主要优势之一是可以在不同尺度下对图像进行分析。高层级的图像层级具有较低的分辨率,但能够捕捉到图像的整体特征和结构信息;而低层级的图像层级具有较高的分辨率,能够提供更多的细节信息。因此,通过在不同层级上对图像进行处理,可以获得更全面和准确的分析结果。尺度空间差分金字塔: 尺度空间差分金字塔是基于高斯金字塔的构建方法。它通过计算高斯金字塔相邻层之间的差分图像来获取尺度空间的特征。这样做可以捕捉到图像中的细节信息,而较高尺度的层级可以揭示出图像中的整体结构。通过在不同层级上进行差分操作,可以凸显出图像中的边缘、角点等局部结构。具体步骤如下: 构建高斯金字塔,并对每一层进行高斯平滑。 对相邻两层图像进行差分操作,得到差分图像。 重复以上步骤,直到达到预定的尺度层数。DOG定义:DoG空间极值检测为了寻找尺度空间的极值点,每个像素点要和其图像域(同一尺度空间)和尺度域(相邻的尺度空间)的所有相邻点进行比较,当其大于(或者小于)所有相邻点时,该点就是极值点。如下图所示,中间的检测点要和其所在图像的3×3邻域8个像素点,以及其相邻的上下两层的3×3领域18个像素点,共26个像素点进行比较。2.2 关键点定位这些候选关键点是DOG空间的局部极值点,而且这些极值点均为离散的点,精确定位极值点的一种方法是:对尺度空间DoG函数进行曲线拟合,计算其极值点,从而实现关键点的精确定位。我们使用泰勒展开来拟合,假设在0点展开,则具体表达式如下所示:用矩阵形式表示如下:2.3 消除边界响应接下来得到了这些极值点具体位置之后,我们还需要对位置进行一些过滤,这里有一个方法就是消除边界响应。因为之前我们通过高斯滤波器对图像进行各种操作,可能会增加一些边界的响应,此时我们需要将其消除掉。在这里啊,修正方法和我们之前介绍边缘检测类似,当一个特征值大,一个特征值小的时候,它就是边界,这里我们定义了一个​和一个​。​=较大特征值,​=较小特征值,组成一个​矩阵,具体如下:当​时,进行过滤操作。2.4 代码示例下面我们看如何在opencv中实现SIFT特征变换*注意:*新版本的opencv不能直接调用,需要降版本为3.4.1,安装命令如下;pip install opencv-python==3.4.1.15pip install opencv-contrib-python==3.4.1.15下面我们来看具体代码:首先读取图片,这里我们还是使用之前的小狗洋气图片import cv2 import numpy as np ​ img = cv2.imread('yangqi.jpg') gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)#灰度图 得到关键点:接下来调用xfeatures2d.SIFT_create()函数初始化SIFT 检测器对象,然后使用detect()函数检测关键点sift = cv2.xfeatures2d.SIFT_create() kp = sift.detect(gray, None)#得到特征点 ​ img = cv2.drawKeypoints(gray, kp, img) #绘制特征点 ​ cv2.imshow('drawKeypoints', img) cv2.waitKey(0) cv2.destroyAllWindows() 计算特征:接下来使用sift.compute()函数计算关键的及其对应的特征kp, des = sift.compute(gray, kp) kp:关键点des:每一个关键点对应的特征接下来我们来看一下他们的纬度print(np.array(kp).shape) #kp是一个列表,需要转换成ndarray print(des.shape) (605,) (605, 128) 结果表示我们一共得到了605个关键点,每个关键点是一个128维的向量。
0
0
0
浏览量442
乌兰推

【Opencv入门到项目实战】(六):图像金字塔

1.图像金字塔图像金字塔是一种多尺度表示的方法,用于在不同分辨率下对图像进行分析和处理。如下图所示:图像金字塔可以将原始图像分解为一系列层级的图像,每个层级都代表了不同尺度的细节信息。比如说我们要做一些图像特征提取,在进行特征提取的时候,我们不仅仅对一张原始输入进行特征提取,而是在图像金字塔每一层当中都进行特征提取,而每一层特征提取出来的结果是不一样的,我们再把特征提取出来的结果融合在一起。图像金字塔主要有两种类型:高斯金字塔和拉普拉斯金字塔。1.1 高斯金字塔1.1.1 向下采样法(缩小)向下采样法是越采样越小,不断降低图像的分辨率,使得图片越变越小,因此它的方向是朝着金字塔顶端。构建过程如下:首先,将原始图像作为第一层(底层)的图像。然后,通过应用高斯滤波器对当前层级的图像进行平滑处理。接着,将平滑后的图像进行下采样,将图像的尺寸缩小一半。重复上述步骤直到达到金字塔的顶层(分辨率最低的层级)。1.1.2​ 向上采样法(放大)向下采样法是越采样越大,不断增加图像的分辨率,使得图片越变越大,因此它的方向是朝着金字塔底端。具体做法如下:使图像在每个方向扩大为原来的两倍,新增的行和列以0填充使用高斯核与放大后的图形进行卷积操作,得到近似值在OpenCV中,我们分别调用cv2.pyrUP()和cv2.pyrDown()来实现上采样和下采样,下面我们来看具体代码import cv2 ​ def cv_show(img,name):    cv2.imshow(name,img)    cv2.waitKey()    cv2.destroyAllWindows() # 查看原始图片 img=cv2.imread("yangqi.jpg") cv_show(img,'img') print (img.shape) (238, 218, 3) 下面我们来分别看下采样和上采样后的结果上采样# 上采样 up=cv2.pyrUp(img) cv_show(up,'up') print (up.shape) #查看上采样后的结果维度 (476, 436, 3) 可以看到,图片变得更大了,并且图像的行和宽都是原来的两倍下采样:# 下采样 down=cv2.pyrDown(img) cv_show(down,'down') print (down.shape) #查看下采样后的结果维度 (119, 109, 3) 可以看到,图片变得更小了,并且图像的行和宽都是原来的一半。小结:高斯金字塔的每一层级都包含了原始图像的低频信息,并且每个层级的分辨率比前一层级低。高斯金字塔可用于图像的缩放、降噪以及图像融合等应用。1.2拉普拉斯金字塔拉普拉斯金字塔是通过高斯金字塔构建的,可以用于重建原始图像,也可以用于实现图像的增强和压缩等操作。构建过程如下:对图像项进行下采样,得到G1,然后经过上采样得到E1,然后用原始图像减去E1即可,我们可以进行多次重复操作得到不同层的结果,下面以生成L1为例,使用opencv代码如下:down=cv2.pyrDown(img) down_up=cv2.pyrUp(down) l_1=img-down_up cv_show(l_1,'l_1') 2.模板匹配模板匹配是指在图像中寻找与给定模板最相似的部分,它的基本思想和卷积原理很像,模板在原图像上从原点开始滑动,计算模板与(图像被模板覆盖的地方)的差别程度,以此来找到最佳匹配位置。这个差别程度的计算方法在opencv里有6种,然后将每次计算的结果放入一个矩阵里,作为结果输出。假如原图形是AxB大小,而模板是axb大小,则输出结果的矩阵是(A-a+1)x(B-b+1)TM_SQDIFF:计算平方不同,计算出来的值越小,越相关TM_CCORR:计算相关性,计算出来的值越大,越相关TM_CCOEFF:计算相关系数,计算出来的值越大,越相关TM_SQDIFF_NORMED:计算归一化平方不同,计算出来的值越接近0,越相关TM_CCORR_NORMED:计算归一化相关性,计算出来的值越接近1,越相关TM_CCOEFF_NORMED:计算归一化相关系数,计算出来的值越接近1,越相关我们先来看两张图# 模板匹配 import cv2 img = cv2.imread('lena.jpg', 0) #目标图片 template = cv2.imread('face.jpg', 0) #要匹配的模板 h, w = template.shape[:2] #模板的大小 img.shape,template.shape ​ ​ ((263, 263), (110, 85)) def cv_show(img,name): cv2.imshow(name,img) cv2.waitKey() cv2.destroyAllWindows() # 显示原始图片 cv_show(img,'img') cv_show(template,'template') 我们想要做的就是将模板匹配到目标图形的位置2.1 获取匹配结果在Opencv中,我们调用cv2.matchTemplate()函数来进行模板匹配,具体语法如下 cv2.matchTemplate(img, template, method) 其中img是传入的图片,template是要匹配的模板,method就是我们刚刚介绍到的几种方法,下面来看具体例子res = cv2.matchTemplate(img, template, cv2.TM_SQDIFF) res.shape (154, 179) 可以看到这里的维度是(154,179),验证了我们之前所介绍的内容,等于(263-110+1,263-85+1)。2.2 获取匹配位置信息匹配后,我们可以通过 cv2.minMaxLoc() 函数获取匹配结果的最大值和最小值,以及对应的位置信息。min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res) min_val,max_val,min_loc,max_loc (39168.0, 74403584.0, (107, 89), (159, 62)) 这里给出了最大最小值和对应的坐标,我们就能根据这些信息绘制出模板匹配的位置。2.3 绘制匹配边框根据最佳匹配位置,可以在原始图像上绘制一个矩形框来标记匹配位置。使用 cv2.rectangle() 函数可以实现这一步骤。我们接下来分别看使用六种不同方法得到的结果import matplotlib.pyplot as plt methods = ['cv2.TM_CCOEFF', 'cv2.TM_CCOEFF_NORMED', 'cv2.TM_CCORR', 'cv2.TM_CCORR_NORMED', 'cv2.TM_SQDIFF', 'cv2.TM_SQDIFF_NORMED'] for i in methods: img2 = img.copy() # 匹配方法的真值 method = eval(i) print (method) res = cv2.matchTemplate(img, template, method) min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res) # 如果是平方差匹配TM_SQDIFF或归一化平方差匹配TM_SQDIFF_NORMED,取最小值 if method in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]: top_left = min_loc else: top_left = max_loc bottom_right = (top_left[0] + w, top_left[1] + h) # 绘制匹配边框 cv2.rectangle(img2, top_left, bottom_right, 255, 2) plt.subplot(121), plt.imshow(res, cmap='gray') plt.xticks([]), plt.yticks([]) # plt.subplot(122), plt.imshow(img2, cmap='gray') plt.xticks([]), plt.yticks([]) plt.suptitle(i) plt.show() 从结果上来看,使用了归一化的几种方法效果都不错。需要注意的是,另外,模板匹配是一种局部匹配方法,对光照变化、旋转变换等情况比较敏感。在某些情况下,可能需要结合其他技术,如特征提取和机器学习等方法,来提高匹配的准确性和鲁棒性。
0
0
0
浏览量627
乌兰推

【Opencv入门到项目实战】(四)Sobel算子|Scharr算子|Laplacian算子

0.引言在图像处理中,梯度是指图像中像素灰度变化的速率或幅度,我们先来看下面这张图假设我们想要计算出A点的梯度,我们可以发现A点位于边缘点,A点左边为黑色,右边为白色,而计算图像的梯度可以提取出图像中的边缘信息,我们常用的方法是使用Sobel算子或Scharr算子进行梯度计算。接下来我们分别来看看具体是如何做的1. Sobel算子和我们之前介绍的各种图像计算的方法类似,我们利用某一个大小的卷积核来进行计算,我们这里也一样,Sobel算子有两个核,一个用于计算图像在水平方向上的差异(x方向梯度),另一个用于计算图像在垂直方向上的差异(y方向梯度)。这两个核可以在水平和垂直方向上检测出图像中的边缘信息。下面是Sobel算子在x和y方向上的核矩阵:我们来看他这个是如何来识别边缘的,以x方向为例,如果两边相差太大了,那么结果的绝对值也会比较大,说明应该在边缘点附近,如果两边值非常接近,则结果也会趋于0,此时说明不在边缘地附近。y方向也是同理。接下来我们看一下如何在Opencv中实现,我们调用cv2.Sobel()函数,dst = cv2.Sobel(src, ddepth, dx, dy, ksize) ddepth:输出图像的深度(数据类型),一般我们指定为64位浮点数型,设为CV_64Fdx和dy分别表示水平和竖直方向ksize是Sobel算子的大小我们以下面这张图为例计算梯度,# 导入原始图 img = cv2.imread('pie.png',cv2.IMREAD_GRAYSCALE) cv2.imshow("img",img) cv2.waitKey() cv2.destroyAllWindows() x方向计算梯度# 定义图像展示函数 def cv_show(img,name):    cv2.imshow(name,img)    cv2.waitKey()    cv2.destroyAllWindows() ​ sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3) cv_show(sobelx,'sobelx') 我们发现只有一半。我们来思考一个问题哈,从黑到白是正数,白到黑就是负数了,所有的负数会被截断成0,所以导致我们右半边的边缘无法显示,因此我们要取绝对值来解决这个问题。我们调用cv2.convertScaleAbs(sobelx) 实现将结果转换为无符号8位整数sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3) sobelx = cv2.convertScaleAbs(sobelx) #实现将结果转换为无符号8位整数 cv_show(sobelx,'sobelx') 现在我们基本找到了边缘,接下来我们还需要看y方向的情况计算y方向的梯度sobely = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=3) sobely = cv2.convertScaleAbs(sobely)   cv_show(sobely,'sobely') 现在我们分别得到了x方向和y方向的边缘,接下来我们进行求和处理。求和调用cv2.addWeighted函数sobelxy = cv2.addWeighted(sobelx,0.5,sobely,0.5,0) cv_show(sobelxy,'sobelxy') 接下来,我们以之前小狗洋气的图片来看一下它的梯度结果# 原始图像 img = cv2.imread('yangqi.jpg',cv2.IMREAD_GRAYSCALE) cv_show(img,'img') 接下来我们来看一下求梯度后的结果img = cv2.imread('yangqi.jpg',cv2.IMREAD_GRAYSCALE) sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3) sobelx = cv2.convertScaleAbs(sobelx) sobely = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=3) sobely = cv2.convertScaleAbs(sobely) sobelxy = cv2.addWeighted(sobelx,0.5,sobely,0.5,0) cv_show(sobelxy,'sobelxy') 可以看到,我们把图片所有的轮廓都给提取出来了。Sobel算子具有简单且易于实现的优点,它对噪声有一定的抑制作用,并可以快速检测出图像中的边缘。然而,Sobel算子也存在一些局限性,如对于较弱的边缘响应不敏感,并且可能会产生较粗的边缘。对于更复杂的场景,可能需要结合其他的边缘检测算法或采用更高级的技术。2. Scharr算子Scharr算子和Sobel算子很像,但在边缘检测方面具有更好的性能。Scharr算子也是基于一阶导数的近似,和Sobel算子一样,Scharr算子也有两个3x3的核、具体核矩阵如下:在Opencv中,我们调用cv2.Scharr()函数实现。scharrx = cv2.Scharr(img,cv2.CV_64F,1,0) scharry = cv2.Scharr(img,cv2.CV_64F,0,1) scharrx = cv2.convertScaleAbs(scharrx)   scharry = cv2.convertScaleAbs(scharry)   scharrxy =  cv2.addWeighted(scharrx,0.5,scharry,0.5,0) cv_show(scharrxy,'scharr') ​从结果来看,与Sobel算子相比,检测出来的边缘更多,因为Scharr算子具有更高的方向敏感性和更好的旋转不变性,能够更准确地检测到边缘,并且在边缘方向变化较大的情况下效果更好。因此,在很多应用中,Scharr算子常常被用作替代Sobel算子的选择。3.Laplacian算子Laplacian算子常用于检测图像中的边缘和纹理,但是它计算图像的二阶导数,以此捕捉到图像中的灰度变化,它只有一个核在Opencv中,我们调用cv2.Laplacian()函数实现,因为这里只有一个核,因此不用分别计算x方向和y方向,直接计算一个即可laplacian = cv2.Laplacian(img,cv2.CV_64F) laplacian = cv2.convertScaleAbs(laplacian)   从结果来看,Laplacian单独使用对边缘检测的效果一般,因为它是一个二阶导数运算,所以图像中的噪声会被放大。因此,在应用Laplacian算子之前,可能需要对图像进行预处理,例如平滑/模糊来降低噪声的影响,我们一般不会单独使用Laplacian算子,而是结合其他的方法使用。
0
0
0
浏览量1882
乌兰推

【Opencv入门到项目实战】(二):图像阈值和平滑处理

1.图像阈值处理在图像处理中,阈值处理是一种常用的技术,可以将图像转换为二值图像,即只有两个像素值(一般为黑色和白色)。OpenCV提供了多种图像阈值处理的方法,接下来我们讨论一下在opencv如何对一个图形进行阈值处理。1.1简单阈值处理(Simple Thresholding)简单阈值处理是一种基本的阈值处理方法,它将图像的每个像素与一个阈值进行比较,并根据比较结果将像素设置为两个值中的一个。在OpenCV中,调用cv2.threshold()来实现,主要包含4个参数: 第一个为输入图,第二个我们设定的阈值,一般为127,那么我们就以127为界进行判断,第三个参数是最大的一个可能值,一般情况下为255。主要就是第四个参数,表示我们要进行的阈值处理及判断条件。这里主要包含5种方法,具体如下cv2.threshold(input, thresh, maxval, type)input: 输入图,只能输入单通道图像,通常来说为灰度图thresh: 阈值maxval: 当像素值超过了阈值(或者小于阈值,根据type来决定),所赋予的值type:二值化操作的类型,包含以下5种类型:cv2.THRESH_BINARY         超过阈值部分取maxval(最大值),否则取0通常情况下,在我们图像当中,越亮的地方值越大,以第一个为例,在这里越亮的地方,超过阈值部分,我取255,是不是相当于把它放到极亮,相当于就是一个白点。否则的话就取零,因此比较暗的地方,小于阈值时我们就直接给它取零,相当于是一个黑的地方。接下来我们来看看这几种阈值处理的效果首先导入一张灰度图import cv2 #opencv读取的格式是BGR import numpy as np import matplotlib.pyplot as plt#Matplotlib是RGB %matplotlib inline img=cv2.imread('yangqi.jpg') #导入你要读取的图片路径 img_gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) #灰度图读取 img_gray.shape (238, 218) 可以看到此时的通道数为1# 其中ret返回的是阈值 ret, thresh1 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY) #超过127的取值为255,否则为0 ret, thresh2 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY_INV)#超过127的取值为0,否则为255 ret, thresh3 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_TRUNC)#超过阈值的设为阈值,否则不变 ret, thresh4 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_TOZERO)#超过127的不变,否则为0 ret, thresh5 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_TOZERO_INV)#超过127的为0,否则不变 titles = ['Original Image', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV'] images = [img, thresh1, thresh2, thresh3, thresh4, thresh5] for i in range(6): plt.subplot(2, 3, i + 1), plt.imshow(images[i], 'gray') plt.title(titles[i]) plt.xticks([]), plt.yticks([]) plt.show() 第一幅图:原始的图像就是这只小狗,可以看出身体比较白,其余地方比较暗。第二幅图:使用BINARY二值法处理的结果,处理完之后大于127亮点的全为白,暗点的全为黑,这个意思这是咱们第一种方法。第三幅图: 使用BINARYINV处理的结果,这和我们第二幅图的结果完全相反,处理完之后大于127亮点的全为黑,暗点的全为白第四幅图: 使用TRUNC截断值处理的结果,这个比较好理解,所有大于127截断使其等于127了,其余的不变第五幅图:使用TOZERO,从名字上就可以理解它的用法,大于阈值的部分保持不变,小于阈值的部分变为0第六幅图:使用TOZERO_INV,和上一种方法的结果刚好相反,大于阈值的变为0,小于阈值的保持不变。1.2自适应阈值处理(Adaptive Thresholding)自适应阈值处理是一种根据图像局部区域的特性自动确定阈值的方法。它将图像分成若干个小区域,并根据每个区域内像素的统计信息来计算阈值。在OpenCV中,可以使用cv2.adaptiveThreshold()函数来实现自适应阈值处理,具体语法如下cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C) 参数说明:src:输入的灰度图像,单通道图像。maxValue:当像素超过阈值时,所设置的最大像素值。adaptiveMethod:自适应方法的类型。可以是以下两种之一:thresholdType:阈值处理的类型。可以是以下两种之一:blockSize:用于计算阈值的像素邻域大小。它必须是奇数,并且大于1。C:在计算阈值时的常数,用于对均值或加权和进行调整。函数的返回值是处理后的二值图像。thresh1 = cv2.adaptiveThreshold(img_gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY,3,3) #这里领域大小为3 thresh2 = cv2.adaptiveThreshold(img_gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY_INV,3,3)#这里领域大小为3 titles = ['Original Image', 'BINARY', 'BINARY_INV'] images = [img_gray, thresh1, thresh2] for i in range(3): plt.subplot(1,3, i + 1), plt.imshow(images[i], 'gray') plt.title(titles[i]) plt.xticks([]), plt.yticks([]) plt.show() 通过自适应阈值处理,我们可以根据每个像素邻域内的统计信息来确定阈值,从而更好地适应不同区域之间的光照变化。这对于处理具有不均匀光照条件的图像非常有用,例如图像中存在阴影或光源不均匀的情况。1.3Otsu's阈值处理Otsu's阈值处理是一种自动确定阈值的方法,它能够找到一个最佳的阈值,使得将图像分割为两个类别后的类别间方差最小化, 这种方法对于没有先验知识的图像分割非常有用。在OpenCV中,我们调用cv2.threshold()函数,并将阈值类型设置为cv2.THRESH_OTSU来实现Otsu's阈值处理。ret, thresh = cv2.threshold(img_gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) titles = ['Original Image', "Otsu's"] images = [img_gray, thresh] for i in range(2): plt.subplot(1,2, i + 1), plt.imshow(images[i], 'gray') plt.title(titles[i]) plt.xticks([]), plt.yticks([]) plt.show() ret 是计算得到的最佳阈值,thresh 是通过应用 Otsu's 方法得到的二值图像。ret 119.0 可以看到,Otsu's方法会自动计算阈值,这里阈值等于119,并将图像分为两个类别(黑色和白色)。使用 Otsu's 阈值处理,我们无需手动选择阈值,而是通过计算来确定最佳阈值,从而实现更准确的图像分割。这对于具有不同光照条件、对比度变化或噪声存在的图像尤其有用。2.平滑处理当我们处理图像时,有时候需要对图像进行平滑处理,以减少噪声、去除细节或者模糊图像,它的具体计算有点类似卷积的计算,在介绍具体方法之前我们先来看一张图片:img = cv2.imread('lenaNoise.png') cv2.imshow('Noise', img) cv2.waitKey(0) cv2.destroyAllWindows() 可以看到这张图片上面有很多椒盐噪声,我们希望能够用平滑方法来处理,在OpenCV提供了多种图形平滑处理方法,下面我们详细介绍一些常用的方法:2.1均值滤波(Mean Filter)将每个像素的邻域像素的平均值作为该像素的新值。这种滤波器对于去除轻度噪声非常有效,但可能会导致图像变得模糊。在OpenCV中,我们调用cv2.blur()函数来实现均值滤波。blur = cv2.blur(img, (3, 3)) #使用3×3的滤波器 cv2.imshow('blur', blur) cv2.waitKey(0) cv2.destroyAllWindows() 可以看见上面的椒盐噪声减弱了一些,没有那么明显,下面我们来看一下它的具体计算逻辑,这里我们选择了3×3的滤波器,因此,每次我们都会看以目标像素为中心的3×3的领域9个值,如下图所示,以204为中心,我们计算包括它在内及其领域共9个值的平均值来做代替。2.2高斯滤波(Gaussian Filter)刚刚我们介绍的均值平衡是简单的求算数平均,而高斯平衡认为其邻域像素的权重不应该一样,越靠近中心的像素权重应该越重,且权重服从高斯分布,然后使用高斯权重来计算邻域像素的加权平均值。相比于均值滤波器,高斯滤波器能够更好地保留图像的细节信息。在OpenCV中,可以使用cv2.GaussianBlur()函数来实现高斯滤波。# 高斯滤波 # 高斯模糊的卷积核里的数值是满足高斯分布,相当于更重视中间的 gaussian = cv2.GaussianBlur(img, (3,3), 1) cv2.imshow('gaussian', gaussian) cv2.waitKey(0) cv2.destroyAllWindows() 2.3中值滤波(Median Filter)中值滤波是一种非线性滤波器,它将每个像素的邻域像素的中值作为该像素的新值。这种滤波器对于去除椒盐噪声非常有效,同时能够保留图像边缘的细节,对于我们给出的这张图而言,效果非常好。在OpenCV中,我们调用cv2.medianBlur()函数来实现中值滤波。# 中值滤波 # 相当于用中值代替 median = cv2.medianBlur(img, 3) # 中值滤波 cv2.imshow('median', median) cv2.waitKey(0) cv2.destroyAllWindows() 可以看出使用中值平滑处理后的效果针对于椒盐噪声非常好。下面我们将三个图放一起来比较# 展示所有的 res = np.hstack((blur,gaussian,median))#按水平方向堆砌,三张图水平方向放一起 cv2.imshow('blur vs gaussian vs median', res) cv2.waitKey(0) cv2.destroyAllWindows() 上述几种方法究竟选哪一个好呢?一般我们需要根据特定的任务和图像特征进行评估和调整,根据我们的需求和图像特点,选择合适的方法来实现图像的平滑处理,例如以本案例来说,使用中值平滑处理的效果是最好的。
0
0
0
浏览量160
乌兰推

【Opencv入门到项目实战】(三):腐蚀操作|膨胀操作|开闭运算

1.腐蚀操作腐蚀操作是图像处理中常用的一种形态学操作,我们通常用于去除图像中的噪声、分割连通区域、减小目标物体的尺寸等。腐蚀操作的原理是,在给定的结构元素下,遍历图像的每个像素,并将其值替换为该像素周围邻域内像素的最小值。结构元素控制了腐蚀的邻域范围和形状。邻域内的任何一个像素为黑色(0),则中心像素也将被置为黑色(0)。这样可以缩小或消除二值图像中的前景目标。在OpenCV中,我们可以使用cv2.erode()来实现腐蚀操作语法如下cv2.erode(src, kernel, iterations) 参数说明:src:输入的二值图像,通常为单通道灰度图像。kernel:腐蚀操作的结构元素,用于定义腐蚀的邻域大小和形状。可以使用 cv2.getStructuringElement() 函数创建不同形状的结构元素。iterations:腐蚀操作的迭代次数,表示应用腐蚀的重复次数。接下来我们先来看一张图:img = cv2.imread('JOJO.png') cv2.imshow('img', img) cv2.waitKey(0) cv2.destroyAllWindows() 大家可以看到,除了JOJO之外,它的周围还有很多斜线,我们可以利用腐蚀操作来消除。# 创建结构元素 (3x3 方框形) kernel = np.ones((3, 3), dtype=np.uint8) # 执行腐蚀操作 eroded = cv2.erode(img, kernel, iterations=1) # 显示结果 cv2.imshow('Eroded Image', eroded) cv2.waitKey(0) cv2.destroyAllWindows() 可以看到,这些斜线变得很浅,因为被他周围的黑色所影响,并且原始图像的字母也变得更小了,因为腐蚀操作减少了一部分信息。我们可以更改iterations的值,来增加迭代次数,迭代的次数越多,则腐蚀的越严重,具体结果如下erosion_1 = cv2.erode(img,kernel,iterations = 1)#1次迭代 erosion_2 = cv2.erode(img,kernel,iterations = 2)#2次迭代 erosion_3 = cv2.erode(img,kernel,iterations = 3)#3次迭代 res = np.hstack((erosion_1,erosion_2,erosion_3))#水平堆砌 # 显示结果 cv2.imshow('res', res) cv2.waitKey(0) cv2.destroyAllWindows() 腐蚀操作和膨胀(Dilation)操作相对应,二者经常组合使用以实现更复杂的形态学图像处理任务,接下来我们来看看膨胀操作。2.膨胀操作说完了腐蚀操作之后,我们再来看一下它的逆操作,膨胀操作。我们在上面的腐蚀操作中,在消除噪声的同时,把有价值的信息也减少了。因此我们希望将这些有价值的信息增大,这样就要利用到膨胀操作。在 OpenCV 中,膨胀操作是通过 cv2.dilate() 函数实现的。该函数接受三个参数:输入图像、结构元素和迭代次数。结构元素可以通过 cv2.getStructuringElement() 函数创建,它定义了膨胀操作的邻域大小和形状。常见的结构元素形状包括矩形、椭圆和十字形。我们还是用刚刚的例子# 原始图像 img = cv2.imread('JOJO.png') cv2.imshow('img', img) cv2.waitKey(0) cv2.destroyAllWindows() 接下来我们使用腐蚀操作消除细线# 腐蚀操作 kernel = np.ones((5,5),np.uint8) dige_erosion = cv2.erode(img,kernel,iterations = 1) cv2.imshow('erosion', erosion) cv2.waitKey(0) cv2.destroyAllWindows() 我们可以看见其中字母也变小了,我们想恢复其原始信息。# 膨胀操作 kernel = np.ones((3,3),np.uint8) dige_dilate = cv2.dilate(dige_erosion,kernel,iterations = 1) cv2.imshow('dilate', dige_dilate) cv2.waitKey(0) cv2.destroyAllWindows() 膨胀操作的效果取决于结构元素的形状和大小,以及迭代次数。增加迭代次数会使目标物体区域更大,边界更粗糙。通常情况下,一个或两个迭代次数就足够了。膨胀操作通常与腐蚀操作结合使用,以在图像中执行形态学处理。这种组合的方法称为开运算(Opening)和闭运算(Closing),接下来我们来看看如何实现3.开运算和闭运算开运算是先进行腐蚀操作,再进行膨胀操作。它主要用于去除图像中的噪点、小的干扰物或者分离连通的对象。闭运算是先进行膨胀操作,再进行腐蚀操作。它主要用于填充图像中的小洞孔或者连接分离的对象。==开运算==:在opencv中,通过调用cv2.morphologyEx()函数,并指定操作类型为cv2.MORPH_OPEN,实现开运算# 开:先腐蚀,再膨胀 img = cv2.imread('dige.png') kernel = np.ones((5,5),np.uint8) opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel) cv2.imshow('opening', opening) cv2.waitKey(0) cv2.destroyAllWindows() 可以看到开运算可以让我们先去除边缘细线,然后再增加信息恢复到原始结果。==闭运算==:在opencv中,通过调用cv2.morphologyEx()函数,并指定操作类型为cv2.MORPH_CLOSE,实现闭运算# 闭:先膨胀,再腐蚀 img = cv2.imread('JOJO.png') kernel = np.ones((5,5),np.uint8) closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel) cv2.imshow('closing', closing) cv2.waitKey(0) cv2.destroyAllWindows()在闭运算中,由于一开始我们加粗了边缘细线,导致后续我们即使做了腐蚀操作也不能完全去除。这两种方法可以用来改善图像的质量、去除噪声或者填充空洞,具体根据我们的需求进行更改。4.礼帽与黑帽礼帽 = 原始输入-开运算结果黑帽 = 闭运算-原始输入通过使用礼帽和黑帽操作,可以突出图像中细微的亮或暗结构,或者检测背景中的亮或暗区域。==礼帽操作==在opencv中,我们通过调用 cv2.morphologyEx() 函数,并指定操作类型为 cv2.MORPH_TOPHAT,执行礼帽操作#礼帽 img = cv2.imread('JOJO.png') tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel) cv2.imshow('tophat', tophat) cv2.waitKey(0) cv2.destroyAllWindows() ==黑帽操作==:黑帽操作是礼帽操作的相反过程,用于突出图像中微小结构或背景中的暗区域。在Opencv中,通过调用 cv2.morphologyEx() 函数,并指定操作类型为cv2.MORPH_BLACKHAT,执行黑帽操作。#黑帽 img = cv2.imread('JOJO.png') blackhat = cv2.morphologyEx(img,cv2.MORPH_BLACKHAT, kernel) cv2.imshow('blackhat ', blackhat ) cv2.waitKey(0) cv2.destroyAllWindows() 5.梯度运算梯度 = 膨胀-腐蚀首先,我们分别实现膨胀和腐蚀操作。# 梯度=膨胀-腐蚀 img = cv2.imread('JOJO.png') kernel = np.ones((5,5),np.uint8) dilate = cv2.dilate(img,kernel,iterations = 1) erosion = cv2.erode(img,kernel,iterations = 1) res = np.hstack((dilate,erosion)) cv2.imshow('res', res) cv2.waitKey(0) cv2.destroyAllWindows() 接下来我们实现==梯度运算==# 梯度运算 gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel) cv2.imshow('gradient', gradient) cv2.waitKey(0) cv2.destroyAllWindows() 大家可以看出这个图片就是用膨胀操作-腐蚀操作的结果。
0
0
0
浏览量2015
乌兰推

【Opencv入门到项目实践】(五):边缘检测

边缘检测在上一篇文章中,我们介绍了图像的梯度结算来检测图像边缘,但是我们之前只是检验了大小,不知道具体方向。使用Canny边缘检测是一种经典而有效的边缘检测算法,Canny边缘检测算法包括以下五大步骤:1.使用高斯滤波器,以平滑图像,滤除噪声首先,对待处理的图像进行高斯滤波以降低噪声的影响。高斯滤波器可以平滑图像并减少局部变化的影响。这里和我们之前在平滑处理中介绍的一样,这里使用的高斯滤波器是归一化后的,具体如下:然后根据这个滤波器,我们就可以计算每一个像素经过高斯平滑后的值2.计算图像中每个像素点的梯度强度和方向在经过高斯滤波后的图像上,使用Sobel算子或其他梯度算子计算图像的梯度大小和方向,表示图像中每个像素的灰度变化率和方向。在水平方向上计算梯度在垂直方向上计算梯度两个方向的Sobel算子如下:两个方向梯度计算结果如下:计算梯度幅值和方向:根据水平和垂直梯度计算每个像素的梯度幅值和方向。这里和我们之前介绍的图像梯度计算中,多了一个方向,我们用​来表示:3.应用非极大值(Non-Maximum Suppression)抑制,以消除边缘检测带来的杂散响应。对于每个像素,判断其是否为边缘像素。具体做法是检查梯度幅值沿着梯度方向上的两侧像素,如果当前像素的梯度幅值最大,则将其保留为边缘像素,否则将其抑制为非边缘像素。4.应用双阈值(Double-Threshold)检测来确定真实的和潜在的边缘根据设定的高阈值和低阈值,对经过非极大值抑制的图像进行分类。如果像素的梯度幅值高于高阈值,则将其确定为强边缘像素;如果像素的梯度幅值低于低阈值,则将其确定为弱边缘像素;如果像素的梯度幅值介于低阈值和高阈值之间,则根据其是否与强边缘像素相连来确定其是否为边缘像素。具体如下图所示5.通过抑制孤立的弱边缘最终完成边缘检测Canny边缘检测算法通过上述步骤,能够在图像中准确地检测到边缘,并且能够剔除噪声和细小的边缘。由于其高精度和可调节的参数,Canny边缘检测广泛应用于图像处理、计算机视觉和模式识别等领域。下面我们调用Opencv中的cv2.Canny()函数实现,具体代码格式如下:edges = cv2.Canny(img, threshold1, threshold2) 其中,threshold1和threshold2是用于双阈值检测的阈值参数。通常,我们设定threshold1的值应该是threshold2的一半或者是三分之一。我们现在来讨论一下这两个参数的影响,当minval设置的越小,我们能检测到的边缘就越多,当maxval设置的越大,我们能检测到的边缘就越少。因此,在实际应用中,我们要根据自己的需求进行调整。下面我们来看一个具体的例子,还是使用小狗洋气的照片,我们将两组阈值分别设为80,160和50,100,看一下他们的区别import cv2 ​ def cv_show(img,name):    cv2.imshow(name,img)    cv2.waitKey()    cv2.destroyAllWindows()     img=cv2.imread("yangqi.jpg",cv2.IMREAD_GRAYSCALE) ​ v1=cv2.Canny(img,80,160) v2=cv2.Canny(img,50,100) ​ res = np.hstack((v1,v2)) #将图片水平堆砌 cv_show(res,'res') ​ 我们对比一下两张图:左边阈值为:80,160右边阈值为:50,100可以看到右边这张图检测到的边缘更多。
0
0
0
浏览量98
乌兰推

【Opencv入门到项目实战】(十):信用卡识别

0.背景介绍接下来我们正式进入项目实战部分,这一章要介绍的是一个信用卡号识别的项目。首先,我们来明确一下研究的问题,假设我们有一张信用卡如下所示,我们要做的就是识别出这上面卡号信息,然后会输出一个序列,第一个序列就是4020,第二序列是3400,第三个序列0234,第四个序列5678,也就是说此时我们不光是把这个数输出来,我们还要知道对应的位置。之前我们已经介绍了Opencv的各种图像基本操作,例如形态学操作、模板匹配,我们现在要做的就是把这些方法全部应用到一起,相当于把我们以前所学的知识点全部穿插到咱们这个项目当中了。我们先来看一下要完成这个项目的基本思想。思考一: 首先最核心的问题是我们如何判断一个数字是几呢?这里我们要用到模板匹配假设我们有一个数字模板如下:现在我们要做的就是将信用卡上每一个数字和模板上的数字进行匹配,看一下它与模板上的哪一个数字最接近,我们就把这个数字输出。因此我们第一步需要得到一个与目标信用卡数字字体非常接近的一个模板。*思考二: *如何每一个数字单独拿出来?我们之前介绍轮廓检测,但是直接得到的轮廓各个数字之间非常不规则,我们可以利用轮廓的外接矩形或者外接圆来进行操作。总体就是分为以上两个步骤,具体过程我们还需要对图像进行各种预处理操作,我们在后面在代码中细致介绍。以下是项目的主要框架,想要源码的可以私信我获取。1.模板处理1.1模板读取首先我们将目标模板读取# 导入工具包 from imutils import contours import numpy as np import argparse import cv2 import myutils ​ ​ # 指定模板和目标图像位置 target = 'images/credit_card_02.png' template = 'images/ocr_a_reference.png' ​ # 定义图像展示函数 def cv_show(name,img): cv2.imshow(name, img) cv2.waitKey(0) cv2.destroyAllWindows() ​ # 读取模板图像 img = cv2.imread(template) ​ cv_show(im) 1.2预处理接下来对模板进行预处理,转换为二值图,因为我们后续轮廓检测时只接受二值图输入。# 灰度图 ref = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) cv_show('ref',ref) ​ # 二值图像 ref = cv2.threshold(ref, 10, 255, cv2.THRESH_BINARY_INV)[1] #阈值设为10 cv_show('ref',ref) 现在得到了二值图像之后我们就可以进行图像轮廓检测了。1.3轮廓计算在这里我们使用cv2.findContours()函数,其只接收一个二值图像,cv2.RETR_EXTERNAL只检测外轮廓,cv2.CHAIN_APPROX_SIMPLE只保留终点坐标。返回参数我们只需要用refCnts即可,它返回的是我们的轮廓信息# 计算轮廓 ​ #返回的list中每个元素都是图像中的一个轮廓 ​ ref_, refCnts, hierarchy = cv2.findContours(ref.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE) ​ cv2.drawContours(img,refCnts,-1,(0,0,255),3) # -1表示绘制所有轮廓 cv_show('img',img) #展示轮廓 ​ ​ 现在我们得到了0-9每个数字的外轮廓信息,但是直接返回的轮廓顺序不一定是按照我们模板从左到右排序的,接下来我们需要对轮廓进行排序,让它按照从左到右0,1,2,3,4…9的顺序排列,这里我们定义了函数,sort_contours,我们直接根据我们的坐标排序,就可以得到按照0-9排列的轮廓import cv2 ​ def sort_contours(cnts, method="left-to-right"):    reverse = False    i = 0 ​    if method == "right-to-left" or method == "bottom-to-top":        reverse = True ​    if method == "top-to-bottom" or method == "bottom-to-top":        i = 1    boundingBoxes = [cv2.boundingRect(c) for c in cnts] #计算外接矩形,用一个最小的矩形,返回x,y,h,w,分别表示坐标和高宽   (cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes),                                        key=lambda b: b[1][i], reverse=reverse))#排序 ​    return cnts, boundingBoxes 现在得到了排序好后的轮廓,接下来我们需要把模板中每个数字轮廓单独拿出来放到一个字典中方便我们后续进行匹配,通过cv.boundingRect()得到轮廓坐标和长宽信息,然后利用我们之前的ROI读取方法即可,最后我们更改一下轮廓的大小。refCnts = sort_contours(refCnts, method="left-to-right")[0] #排序,从左到右 digits = {} ​ # 遍历每一个轮廓 for (i, c) in enumerate(refCnts): # 计算外接矩形并且resize成合适大小 (x, y, w, h) = cv2.boundingRect(c) roi = ref[y:y + h, x:x + w] roi = cv2.resize(roi, (57, 88)) ​ # 每一个数字对应每一个模板 digits[i] = roi 接下来我们就得到了每个数字模板的轮廓信息,并保留在digits字典中,接下来我们需要对输入图像进行处理。2.输入图像处理2.1图形读取这里我们初始化了两个卷积核,分别为9×3和5×5的,这里大家可以根据自己的任务更换别的卷积核大小。然后我们把目标图像读取进来并转换为灰度图# 初始化卷积核 rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3)) sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)) ​ ​ #读取输入图像,预处理 ​ image = cv2.imread(target) cv_show('image',image) image = myutils.resize(image, width=300) gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 转换为灰度图 cv_show('gray',gray) 2.2预处理得到灰度图之后,我们需要进行更细节的预处理,因为我们想要检测的是银行卡号,我们关注的是这样的数字部分,也就是更亮的区域,因此我们在这里进行了礼帽操作,来突出我们想要研究的信息,在实际应用中,可以根据具体想要研究的任务来选择其他的处理方式#礼帽操作,突出更明亮的区域 tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, rectKernel) cv_show('tophat',tophat) 接下来我们进一步的利用sobel算子来进行边缘检测gradX = cv2.Sobel(tophat, ddepth=cv2.CV_32F, dx=1, dy=0, #ksize=-1相当于用3*3的 ksize=-1) # 使用Sobel算子处理 ​ ​ gradX = np.absolute(gradX) (minVal, maxVal) = (np.min(gradX), np.max(gradX)) gradX = (255 * ((gradX - minVal) / (maxVal - minVal))) # 归一化处理 gradX = gradX.astype("uint8") ​ ​ print (np.array(gradX).shape) cv_show('gradX',gradX) 得到边缘之后,我们希望将这些数字分块放到一起,每四个数字为一个小方块。我们可以利用之前介绍过的先膨胀,再腐蚀的操作。#通过闭操作(先膨胀,再腐蚀)将数字连在一起 gradX = cv2.morphologyEx(gradX, cv2.MORPH_CLOSE, rectKernel) cv_show('gradX',gradX) #二值化处理:THRESH_OTSU会自动寻找合适的阈值,适合双峰,需把阈值参数设置为0 thresh = cv2.threshold(gradX, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] cv_show('thresh',thresh) #再来一个闭操作 thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, sqKernel) #再来一个闭操作 cv_show('thresh',thresh) 首先经过一个闭操作后,得到下列结果然后我们进一步使用二值化处理,将图片转换为二值图像现在得到的结果中,还有一部分空隙,我们再进行一次闭操作,得到结果如下:现在得到的结果是一个完全闭合的状态了,此时我们再检测它的外轮廓,会更准确一些。2.3轮廓计算接下来我们计算轮廓,和之前在处理模板一样,我们调用cv2.findContours()函数计算轮廓信息。# 计算轮廓 thresh_, threshCnts, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)# cnts = threshCnts #轮廓信息 cur_img = image.copy() cv2.drawContours(cur_img,cnts,-1,(0,0,255),3)#在原始图像上绘制轮廓 cv_show('img',cur_img) 这里需要说明的是,我们这里是用经过一切预处理后得到的轮廓信息,然后绘制在原始图像中。但是我们得到的轮廓有点多,且有一些不太规则的形状,有些可能不是我们想要轮廓,我们需要把这些轮廓过滤,我们只要四组数字轮廓。我们可以根据他们的坐标位置进行筛选,具体的筛选范围大家要根据自己的实际任务选择.locs = [] # 遍历轮廓 for (i, c) in enumerate(cnts): # 计算矩形 (x, y, w, h) = cv2.boundingRect(c) ar = w / float(h) # 选择合适的区域,根据实际任务来,这里的基本都是四个数字一组 if ar > 2.5 and ar < 4.0: #这里需要根据具体的任务更改,我这里是经过几次尝试测试出来的 if (w > 40 and w < 55) and (h > 10 and h < 20): #符合的留下来 locs.append((x, y, w, h)) # 将符合的轮廓从左到右排序 locs = sorted(locs, key=lambda x:x[0]) locs [(34, 111, 47, 14), (95, 111, 48, 14), (157, 111, 47, 14), (219, 111, 48, 14)] 可以看到我们现在得到了四个轮廓,并且进行了排序,接下来我们怎么进行模板匹配呢?我们不是拿这四个大轮廓去匹配,而是在每一个大轮廓中,再去分隔成小轮廓,然后去和我们之前保存的10个数的模板进行匹配。2.4计算匹配得分接下来我们要做的是去遍历每一个轮廓当中的数字,然后将其与模板中的10个数字计算匹配得分,从而识别出对于的数字,我们先来看第一个轮廓:有了这个之后,就和我们最开始处理模板一样,先进行二值化处理,得到下图然后计算每一组的轮廓,并按照从左到右的顺序排列,以第一个数字为例,得到结果如下然后我们就是将这每一个数字与模板上的10个数字进行对比,看一下和哪一个最相似。具体做法跟之前都是一样的吧,先去找到外接矩形,然后对外接矩形进行resize。然后我们就要计算得分了,我们利用模板匹配中的方法,使用cv2.TM_CCOEFF计算得分,然后找到最匹配的数字,这样就完成了我们所有的步骤了output = [] # 遍历每一个轮廓中的数字 for (i, (gX, gY, gW, gH)) in enumerate(locs): # initialize the list of group digits groupOutput = [] # 根据坐标提取每一个组 group = gray[gY - 5:gY + gH + 5, gX - 5:gX + gW + 5] # 扩张一下轮廓 cv_show('group',group) # 预处理 group = cv2.threshold(group, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] cv_show('group',group) # 计算每一组的轮廓 group_,digitCnts,hierarchy = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) digitCnts = contours.sort_contours(digitCnts, method="left-to-right")[0] # 计算每一组中的每一个数值 for c in digitCnts: # 找到当前数值的轮廓,resize成合适的的大小 (x, y, w, h) = cv2.boundingRect(c) roi = group[y:y + h, x:x + w] roi = cv2.resize(roi, (57, 88)) cv_show('roi',roi) # 计算匹配得分 scores = [] # 在模板中计算每一个得分 for (digit, digitROI) in digits.items(): # 模板匹配 result = cv2.matchTemplate(roi, digitROI, cv2.TM_CCOEFF) (_, score, _, _) = cv2.minMaxLoc(result) scores.append(score) # 得到最合适的数字 groupOutput.append(str(np.argmax(scores))) # 绘制结果 cv2.rectangle(image, (gX - 5, gY - 5), (gX + gW + 5, gY + gH + 5), (0, 0, 255), 1) cv2.putText(image, "".join(groupOutput), (gX, gY - 15), cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2) # 得到结果 output.extend(groupOutput) # 打印结果 print("Credit Card: {}".format("".join(output))) cv2.imshow("Image", image) cv2.waitKey(0) Credit Card: 4020340002345678 3.小结我们这个项目主要分两步。第一步,定位到目标数字在什么位置。第二步,基于定位好的区域,在模板当中去匹配它到底是一个什么样的值。中间利用了我们之前介绍过的各种图像处理方法。今天我们这个项目是做一个信用卡号识别,如果说大家想做车牌识别、学生卡、身份证识别,都是一个类似的做法,我们主需要更改一下对于的照片模板以及其中的一些参数即可。
0
0
0
浏览量1746
乌兰推

Opencv入门到项目实战

OpenCV是一个强大的计算机视觉库,它提供了丰富的函数和工具,可用于图像处理、特征提取、目标检测、机器学习等各种计算机视觉任务
0
0
0
浏览量2235
乌兰推

三大经典卷积神经网络架构:LeNet、AlexNet、VGG(代码实现及案例比较)

1.Lenet1.1理论介绍经过前面的介绍,我们已经了解了卷积神经网络的基本模块,接下来我们来讨论几个经典的神经网络结构,首先介绍LeNet-5。LeNet是最早的卷积神经网络之一,其被提出用于识别手写数字和机器印刷字符。1998年,Yann LeCun第一次将LeNet卷积神经网络应用到图像分类上,在手写数字识别任务中取得了巨大成功。首先看看LeNet-5的网络结构,下图是原论文放出来的架构假设你有一张32×32×1的图片,LeNet-5可以识别图中的手写数字。由于LeNet-5是针对灰度图片训练的,所以图片的大小只有32×32×1。LeNet的结构如下:**输入层:**接收输入图像的尺寸为32x32x1。卷积层部分:        卷积层1:6×5x5的卷积核,步长为1,填充为0,使用Sigmoid激活函数平均池化层1:2x2的池化窗口,步长为2。卷积层2:16×5x5的卷积核,步长为1,填充为0,使用Sigmoid激活函数。平均池化层2:2x2的池化窗口,步长为2。全连接层部分:全连接层1:120个神经元,使用Sigmoid激活函数。全连接层2:84个神经元,使用Sigmoid激活函数。全连接层3(输出层):10个神经元,对应10个手写数字类别,现在往往用softmax。总的来说,如果我们从左往右看,随着网络越来越深,图像的高度和宽度在缩小,从最初的32×32缩小到28×28,再到14×14、10×10,最后只有5×5。与此同时,随着网络层次的加深,通道数量一直在增加,从1增加到6个,再到16个。这个神经网络中还有一种模式至今仍然经常用到,就是一个或多个卷积层后面跟着一个池化层,然后又是若干个卷积层再接一个池化层,然后是全连接层,最后是输出,这种排列方式很常用。Fashion—MNIST数据集原始的LeNet是在MNIST数据集上实现的,但是MNIST数据集在今天来说实在太简单了,我们使用一个稍微复杂一点的数据集Fashion-MNIST,为了方便我们后续比较几个模型的性能。Fashion-MNIST数据集由Zalando Research创建,并且与经典的MNIST数据集具有相似的结构。它包含了来自10个不同类别的共计70000张灰度图像,每个类别包含7000张图像。这些类别分别是:T恤、裤子、套头衫、连衣裙、外套、凉鞋、衬衫、运动鞋、包和短靴。每张图像的尺寸为28x28像素,并以灰度形式表示,像素值范围在0到255之间。Fashion-MNIST数据集已经被标记,因此每个图像都与其对应的类别标签相关联。这使得Fashion-MNIST成为评估机器学习模型在图像分类任务上表现的理想数据集。Fashion-MNIST的目标是提供一个更具挑战性的数据集,用于测试和比较不同算法的性能。与MNIST数据集相比,Fashion-MNIST涵盖更复杂、多样化的图像内容,更能反映现实世界中的图像分类问题。我们来简单的看一下数据集,我们可以利用torchvision来下载import torch import torchvision import matplotlib.pyplot as plt # 加载Fashion-MNIST数据集 train_set = torchvision.datasets.FashionMNIST(root='./data', train=True, download=True) test_set = torchvision.datasets.FashionMNIST(root='./data', train=False, download=True) # 查看数据集大小 print(f"训练集大小: {len(train_set)}") print(f"测试集大小: {len(test_set)}") # 获取类别标签 labels = train_set.classes print(f"类别标签: {labels}") # 随机显示几个样本图像 fig, axes = plt.subplots(2, 5, figsize=(10, 4)) for i, ax in enumerate(axes.flat): image, label = train_set[i] ax.imshow(image, cmap='gray') ax.set_title(labels[label]) ax.axis('off') plt.show() 可以看到上面10张示例图,相对于手写数字识别(MNIST)数据集而言,更复杂一些,下面我们正式使用LeNet来对Fashion-MNIST数据集进行识别。1.2代码实现1.导入相关库:import torch import torch.nn as nn import torch.optim as optim import torchvision import torchvision.transforms as transforms import matplotlib.pyplot as plt 2.定义LeNet框架# 定义 LeNet 模型 class LeNet(nn.Module): def __init__(self): super(LeNet, self).__init__() self.conv1 = nn.Conv2d(1, 6, kernel_size=5,padding=2) self.avgpool = nn.AvgPool2d(kernel_size=2, stride=2) self.conv2 = nn.Conv2d(6, 16, kernel_size=5) self.fc1 = nn.Linear(16 * 5 * 5, 120) self.fc2 = nn.Linear(120, 84) self.fc3 = nn.Linear(84, 10) def forward(self, x): out = self.avgpool(torch.relu(self.conv1(x))) out = self.avgpool(torch.relu(self.conv2(out))) out = out.view(out.size(0), -1) out = torch.sigmoid(self.fc1(out)) out = torch.sigmoid(self.fc2(out)) out = self.fc3(out) return out 请注意,在整个卷积块中,与上一层相比,每一层特征的高度和宽度都减小了,因此高度和宽度都减少了4个像素。 随着层叠的上升,通道的数量从输入时的1个,增加到第一个卷积层之后的6个,再到第二个卷积层之后的16个。 同时,每个池化层层的高度和宽度都减半。最后,每个全连接层减少维数,最终输出一个维数与结果分类数相匹配的输出。设置gpu# 设置gpu device = torch.device("cuda" if torch.cuda.is_available() else "cpu") 3.导入Fashion-MINIST数据集# 加载 Fashion-MNIST 数据集 transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,)) ]) trainset = torchvision.datasets.FashionMNIST(root='./data', train=True, download=True, transform=transform) trainloader = torch.utils.data.DataLoader(trainset, batch_size=64, shuffle=True, num_workers=2) testset = torchvision.datasets.FashionMNIST(root='./data', train=False, download=True, transform=transform) testloader = torch.utils.data.DataLoader(testset, batch_size=64, shuffle=False, num_workers=2) 4.初始化模型# 初始化模型、损失函数和优化器 model = LeNet().to(device) criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(model.parameters(), lr=0.9) 这里我们使用交叉熵损失函数和小批量梯度下降5.模型训练和评估num_epochs = 10 train_losses = [] test_losses = [] for epoch in range(num_epochs): train_loss = 0.0 test_loss = 0.0 correct = 0 total = 0 # 训练模型 model.train() for images, labels in trainloader: images, labels = images.to(device), labels.to(device) optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels) loss.backward() optimizer.step() train_loss += loss.item() # 测试模型 model.eval() with torch.no_grad(): for images, labels in testloader: images, labels = images.to(device), labels.to(device) outputs = model(images) loss = criterion(outputs, labels) test_loss += loss.item() _, predicted = torch.max(outputs.data, 1) total += labels.size(0) correct += (predicted == labels).sum().item() avg_train_loss = train_loss / len(trainloader) avg_test_loss = test_loss / len(testloader) train_losses.append(avg_train_loss) test_losses.append(avg_test_loss) print(f"Epoch [{epoch+1}/{num_epochs}], Train Loss: {avg_train_loss:.4f}, Test Loss: {avg_test_loss:.4f}, Acc: {correct/total*100:.2f}%") # 绘制测试误差和训练误差曲线 plt.plot(train_losses, label='Training Loss') plt.plot(test_losses, label='Testing Loss') plt.xlabel('Epoch') plt.ylabel('Loss') plt.legend() plt.show() Epoch [1/10], Train Loss: 2.2963, Test Loss: 2.2134, Acc: 30.00% Epoch [2/10], Train Loss: 0.9418, Test Loss: 0.6950, Acc: 75.43% Epoch [3/10], Train Loss: 0.5754, Test Loss: 0.5239, Acc: 80.05% Epoch [4/10], Train Loss: 0.4852, Test Loss: 0.4512, Acc: 83.23% Epoch [5/10], Train Loss: 0.4302, Test Loss: 0.4255, Acc: 84.22% Epoch [6/10], Train Loss: 0.3905, Test Loss: 0.3730, Acc: 85.98% Epoch [7/10], Train Loss: 0.3644, Test Loss: 0.3640, Acc: 86.68% Epoch [8/10], Train Loss: 0.3424, Test Loss: 0.3370, Acc: 87.41% Epoch [9/10], Train Loss: 0.3253, Test Loss: 0.3261, Acc: 87.83% Epoch [10/10], Train Loss: 0.3107, Test Loss: 0.3042, Acc: 88.74% 2. AlexNet2.1 理论介绍AlexNet,是以论文的第一作者Alex Krizhevsky的名字命名的,另外两位合著者是ilya Sutskever和Geoffery Hinton。AlexNet在2012年在ImageNet图像分类挑战赛上取得了突破性的成果,其本质上和LeNet没有区别,可以看做是一个更深的LeNet,拥有更多的参数。AlexNet首先用一张227×227×3图像,论文中实际用的是224×224×3,实践中往往227×227×3更有效,我们来看一下ALexNet的基本框架**输入层:**接收输入图像的尺寸为227x227x3。卷积层部分:全连接层部分:实际上,AlexNet神经网络与LeNet有很多相似之处,不过AlexNet要大得多。正如前面讲到的LeNet大约有6万个参数,而AlexNet模型总共有5个卷积层,3个池化层和3个全连接层,参数量较大,约6000万个参数。同时,通过大输入图像尺寸和大尺寸的卷积核,使得网络能够更好地捕捉图像中的细节信息。此外,AlexNet比LeNet表现更为出色的另一个原因是它使用了ReLu激活函数,以及在池化层中使用了maxpooling。于此同时,AlexNet引入了深度学习中的一些重要概念和技术,如使用ReLU激活函数、局部响应归一化(LRN)和Dropout正则化等。2.2 代码实现原文中AlexNet是在ImageNet上进行训练的,但是这里为了方便比较,以及节约训练时间,我们依旧在Fashion—MNIST数据集上进行训练。1.定义AlexNet模型#定义AlexNet class AlexNet(nn.Module): def __init__(self, num_classes=10): super(AlexNet, self).__init__() self.features = nn.Sequential( nn.Conv2d(1, 96, kernel_size=11, stride=4), nn.ReLU(inplace=True), nn.MaxPool2d(kernel_size=3, stride=2), nn.Conv2d(96, 256, kernel_size=5, padding=2), nn.ReLU(inplace=True), nn.MaxPool2d(kernel_size=3, stride=2), nn.Conv2d(256, 384, kernel_size=3, padding=1), nn.ReLU(inplace=True), nn.Conv2d(384, 384, kernel_size=3, padding=1), nn.ReLU(inplace=True), nn.Conv2d(384, 256, kernel_size=3, padding=1), nn.ReLU(inplace=True), nn.MaxPool2d(kernel_size=3, stride=2) ) self.classifier = nn.Sequential( nn.Dropout(p=0.5), nn.Linear(256 * 6 * 6, 4096), nn.ReLU(inplace=True), nn.Dropout(p=0.5), nn.Linear(4096, 4096), nn.ReLU(inplace=True), nn.Linear(4096, num_classes) ) def forward(self, x): x = self.features(x) x = x.view(x.size(0), 256 * 6 * 6) x = self.classifier(x) return x 2.加载数据集 # 加载 Fashion-MNIST 数据集 transform = transforms.Compose([ transforms.ToTensor(), transforms.Resize((227,227)),#将原始图像扩宽到227×227 transforms.Normalize((0.5,), (0.5,)) ]) trainset = torchvision.datasets.FashionMNIST(root='./data', train=True, download=True, transform=transform) train_loader = torch.utils.data.DataLoader(trainset, batch_size=64, shuffle=True, num_workers=2) testset = torchvision.datasets.FashionMNIST(root='./data', train=False, download=True, transform=transform) test_loader = torch.utils.data.DataLoader(testset, batch_size=64, shuffle=False, num_workers=2) 3.初始化AlexNet模型# 初始化AlexNet模型 model = AlexNet().to(device) criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(model.parameters(), lr=0.01) 由于Fashion-MNINST图像默认是28×28的,我们需要将其增加到227×227,在实际中我们一般不会这样做。4.模型训练和评估# 训练AlexNet模型 num_epochs = 10 train_losses = [] test_losses = [] for epoch in range(num_epochs): train_loss = 0.0 test_loss = 0.0 correct = 0 total = 0 # 训练模型 model.train() for images, labels in train_loader: images, labels = images.to(device), labels.to(device) optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels) loss.backward() optimizer.step() train_loss += loss.item() # 测试模型 model.eval() with torch.no_grad(): for images, labels in test_loader: images, labels = images.to(device), labels.to(device) outputs = model(images) loss = criterion(outputs, labels) test_loss += loss.item() _, predicted = torch.max(outputs.data, 1) total += labels.size(0) correct += (predicted == labels).sum().item() avg_train_loss = train_loss / len(train_loader) avg_test_loss = test_loss / len(test_loader) train_losses.append(avg_train_loss) test_losses.append(avg_test_loss) print(f"Epoch [{epoch+1}/{num_epochs}], Train Loss: {avg_train_loss:.4f}, Test Loss: {avg_test_loss:.4f}, Acc: {correct/total*100:.2f}%") # 绘制测试误差和训练误差曲线 plt.plot(train_losses, label='Training Loss') plt.plot(test_losses, label='Testing Loss') plt.xlabel('Epoch') plt.ylabel('Loss') plt.legend() plt.show() Epoch [1/10], Train Loss: 0.8024, Test Loss: 1.3865, Acc: 55.48% Epoch [2/10], Train Loss: 0.4890, Test Loss: 0.4372, Acc: 83.66% Epoch [3/10], Train Loss: 0.3692, Test Loss: 0.3832, Acc: 85.42% Epoch [4/10], Train Loss: 0.3307, Test Loss: 0.3728, Acc: 86.03% Epoch [5/10], Train Loss: 0.3030, Test Loss: 0.3281, Acc: 87.72% Epoch [6/10], Train Loss: 0.2829, Test Loss: 0.3285, Acc: 87.78% Epoch [7/10], Train Loss: 0.2697, Test Loss: 0.3515, Acc: 87.47% Epoch [8/10], Train Loss: 0.2560, Test Loss: 0.3193, Acc: 88.24% Epoch [9/10], Train Loss: 0.2466, Test Loss: 0.3005, Acc: 89.02% Epoch [10/10], Train Loss: 0.2373, Test Loss: 0.3068, Acc: 89.00% 可以看出AlexNet在Fashion-MNIST数据集上测试精度有所提升,突破了89%,这是因为AlexNet使用了更深更大的网络。3.VGG3.1 理论介绍经过Lenet和AlexNet的介绍,我们可以发现使用更深更大的神经网络,能够带来更好的效果,这也是目前深度学习领域一直在做的事情,包括现在热门gpt4。但是AlexNet有一个问题是框架的设置太不规则,因此如何更好的设计更深更大的神经网络值得我们去思考。为了实现这一目的,VGG模型(Visual Geometry Group)产生了,VGG由牛津大学的研究团队开发的深度卷积神经网络模型。VGG模型在2014年的ImageNet图像分类挑战赛中取得了很大的成功,并且在计算机视觉领域被广泛应用。VGG模型的主要特点是它采用了非常小的卷积核(3x3)和最大池化层(2x2),以及多个卷积和池化层的叠加。模型的深度可变,通过调整卷积和全连接层的数量来改变模型的深度。最常用的VGG模型有VGG16和VGG19。VGG模型的主要优势是它具有非常好的表达能力和一致性,以及相对简单的结构。它通过多层卷积和池化层来逐渐提取图像的特征,并通过全连接层进行分类。这种结构使得模型能够捕获不同尺度下的图像特征,从而提高了模型的准确性,下面我们具体讲讲这种网络结构,如下图所示(VGG16)VGG模型主要由VGG块和全连接层组成,通过多次叠加这些层来逐渐提取图像特征并进行分类。其中每一个VGG块都是由3×3的卷积层和2×2的池化层组成下面是VGG16模型的详细结构:输入层:接收输入图像的尺寸为224x224x3。VGG块部分: 卷积层1-1:64个3x3的卷积核,填充为1,ReLU激活函数。 卷积层1-2:64个3x3的卷积核,填充为1,ReLU激活函数。 最大池化层1:2x2的池化窗口,步长为2 卷积层2-1:128个3x3的卷积核,填充为1,ReLU激活函数。 卷积层2-2:128个3x3的卷积核,填充为1,ReLU激活函数。 最大池化层2:2x2的池化窗口,步长为2。 卷积层3-1:256个3x3的卷积核,填充为1,ReLU激活函数。 卷积层3-2:256个3x3的卷积核,填充为1,ReLU激活函数。 卷积层3-3:256个3x3的卷积核,填充为1,ReLU激活函数。 最大池化层3:2x2的池化窗口,步长为2。 卷积层4-1:512个3x3的卷积核,填充为1,ReLU激活函数。 卷积层4-2:512个3x3的卷积核,填充为1,ReLU激活函数。 卷积层4-3:512个3x3的卷积核,填充为1,ReLU激活函数。 最大池化层4:2x2的池化窗口,步长为2。 卷积层5-1:512个3x3的卷积核,填充为1,ReLU激活函数。 卷积层5-2:512个3x3的卷积核,填充为1,ReLU激活函数。 卷积层5-3:512个3x3的卷积核,填充为1,ReLU激活函数。 最大池化层5:2x2的池化窗口,步长为2。全连接层部分:全连接层1:4096个神经元,ReLU激活函数。 Dropout层1:以0.5的概率随机将输入置为0。 全连接层2:4096个神经元,ReLU激活函数。 Dropout层2:以0.5的概率随机将输入置为0。 全连接层3(输出层):1000个神经元,对应ImageNet的1000个类别。VGG16模型总共有13个卷积层和3个全连接层,参数量较大。该模型的设计思想是通过多层的小卷积核和池化层来逐渐缩小宽度,并提取出更高级别的图像特征。同时,使用ReLU激活函数来增强网络的非线性表达能力。最后通过全连接层进行分类。随着网络的加深,图像的高度和宽度都在以一定的规律不断缩小,每次池化后刚好缩小一半,而通道数量在不断增加,而且刚好也是在每组卷积操作后增加一倍。也就是说,图像缩小的比例和通道数增加的比例是有规律的。VGG使用可重复使用的卷积块构建深度卷积神经网络不同的卷积块个数和超参数可以得到不同系列的VGG(如:VGG16、VGG19)3.2代码实现1.VGG模型定义原文VGG16模型定义如下# 定义VGG16模型 class VGG16(nn.Module): def __init__(self, num_classes=10): super(VGG16, self).__init__() self.features = nn.Sequential( nn.Conv2d(1, 64, kernel_size=3, padding=1),nn.ReLU(inplace=True), nn.Conv2d(64, 64, kernel_size=3, padding=1),nn.ReLU(inplace=True), nn.MaxPool2d(kernel_size=2, stride=2), nn.Conv2d(64, 128, kernel_size=3, padding=1),nn.ReLU(inplace=True), nn.Conv2d(128, 128, kernel_size=3, padding=1),nn.ReLU(inplace=True), nn.MaxPool2d(kernel_size=2, stride=2), nn.Conv2d(128, 256, kernel_size=3, padding=1),nn.ReLU(inplace=True), nn.Conv2d(256, 256, kernel_size=3, padding=1),nn.ReLU(inplace=True), nn.Conv2d(256, 256, kernel_size=3, padding=1),nn.ReLU(inplace=True), nn.MaxPool2d(kernel_size=2, stride=2), nn.Conv2d(256, 512, kernel_size=3, padding=1),nn.ReLU(inplace=True), nn.Conv2d(512, 512, kernel_size=3, padding=1),nn.ReLU(inplace=True), nn.Conv2d(512, 512, kernel_size=3, padding=1),nn.ReLU(inplace=True), nn.MaxPool2d(kernel_size=2, stride=2), nn.Conv2d(512, 512, kernel_size=3, padding=1),nn.ReLU(inplace=True), nn.Conv2d(512, 512, kernel_size=3, padding=1),nn.ReLU(inplace=True), nn.Conv2d(512, 512, kernel_size=3, padding=1),nn.ReLU(inplace=True), nn.MaxPool2d(kernel_size=2, stride=2) ) self.classifier = nn.Sequential( nn.Linear(512 * 7 * 7, 4096),nn.ReLU(inplace=True), nn.Dropout(), nn.Linear(4096, 4096),nn.ReLU(inplace=True), nn.Dropout(), nn.Linear(4096, num_classes) ) def forward(self, x): x = self.features(x) x = x.view(x.size(0), 512 * 7 * 7) x = self.classifier(x) return x 这样看上去有点冗余,为了方便更改架构,我们可以设置VGG块,然后根据VGG块来生成网络,后续的很多网络都用类似的想法。 VGG块定义如下import torch import torch.nn as nn def vgg_block(num_convs, in_channels, out_channels): layers = [] for _ in range(num_convs): layers.append(nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1)) layers.append(nn.ReLU()) in_channels = out_channels layers.append(nn.MaxPool2d(kernel_size=2,stride=2)) return nn.Sequential(*layers) 由于这里使用的数据集数量较小,考虑到性能问题,这里我们使用VGG-11,共有8个卷积层和3个全连接层。conv_arch = ((1, 64), (1, 128), (2, 256), (2, 512), (2, 512)) def vgg(conv_arch): conv_blks = [] in_channels = 1 # 卷积层部分 for (num_convs, out_channels) in conv_arch: conv_blks.append(vgg_block(num_convs, in_channels, out_channels)) in_channels = out_channels return nn.Sequential( *conv_blks, nn.Flatten(), # 全连接层部分 nn.Linear(out_channels * 7 * 7, 4096), nn.ReLU(), nn.Dropout(0.5), nn.Linear(4096, 4096), nn.ReLU(), nn.Dropout(0.5), nn.Linear(4096, 10)) net = vgg(conv_arch) 2.加载Fashion-MNIST数据集import torch import torch.nn as nn import torch.optim as optim import torchvision import torchvision.transforms as transforms import matplotlib.pyplot as plt # 设置使用的设备为GPU,如果没有GPU则使用CPU device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 加载Fashion-MNIST数据集 transform = transforms.Compose([ transforms.ToTensor(), transforms.Resize((224,224)),# transforms.Normalize((0.5,), (0.5,)) ]) trainset = torchvision.datasets.FashionMNIST(root='./data', train=True,download=True, transform=transform) trainloader = torch.utils.data.DataLoader(trainset, batch_size=64,shuffle=True, num_workers=2) testset = torchvision.datasets.FashionMNIST(root='./data', train=False,download=True, transform=transform) testloader = torch.utils.data.DataLoader(testset, batch_size=64,shuffle=False, num_workers=2) 由于VGG输入图像要求为224×224,这里需要将Fashion—MNIST的图像大小更改,使用transforms.Resize函数。3.初始化模型model = net.to(device) criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(model.parameters(), lr=0.05) 4.模型训练和评估# 训练模型 num_epochs = 10 train_losses = [] test_losses = [] train_accs = [] test_accs = [] for epoch in range(num_epochs): train_loss = 0.0 train_total = 0 train_correct = 0 model.train() for images, labels in trainloader: images, labels = images.to(device), labels.to(device) optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels) loss.backward() optimizer.step() train_loss += loss.item() _, predicted = torch.max(outputs.data, 1) train_total += labels.size(0) train_correct += (predicted == labels).sum().item() train_loss /= len(trainloader) train_accuracy = 100.0 * train_correct / train_total train_losses.append(train_loss) train_accs.append(train_accuracy) test_loss = 0.0 test_total = 0 test_correct = 0 model.eval() with torch.no_grad(): for images, labels in testloader: images, labels = images.to(device), labels.to(device) outputs = model(images) loss = criterion(outputs, labels) test_loss += loss.item() _, predicted = torch.max(outputs.data, 1) test_total += labels.size(0) test_correct += (predicted == labels).sum().item() test_loss /= len(testloader) test_accuracy = 100.0 * test_correct / test_total test_losses.append(test_loss) test_accs.append(test_accuracy) print(f"Epoch {epoch+1}/{num_epochs}: Train Loss: {train_loss:.4f}, Train Acc: {train_accuracy:.2f}%, Test Loss: {test_loss:.4f}, Test Acc: {test_accuracy:.2f}%") # 绘制训练误差和测试误差曲线 plt.figure(figsize=(10, 5)) plt.plot(range(1, num_epochs+1), train_losses, label='Train Loss') plt.plot(range(1, num_epochs+1), test_losses, label='Test Loss') plt.xlabel('Epoch') plt.ylabel('Loss') plt.title('Training and Testing Loss') plt.legend() plt.show() # 绘制训练准确率和测试准确率曲线 plt.figure(figsize=(10, 5)) plt.plot(range(1, num_epochs+1), train_accs, label='Train Acc') plt.plot(range(1, num_epochs+1), test_accs, label='Test Acc') plt.xlabel('Epoch') plt.ylabel('Accuracy') plt.title('Training and Testing Accuracy') plt.legend() plt.show() Epoch 1/10: Train Loss: 2.2078, Train Acc: 15.18%, Test Loss: 0.9984, Test Acc: 65.97% Epoch 2/10: Train Loss: 0.5435, Train Acc: 79.75%, Test Loss: 0.4182, Test Acc: 84.12% Epoch 3/10: Train Loss: 0.3391, Train Acc: 87.61%, Test Loss: 0.3074, Test Acc: 88.30% Epoch 4/10: Train Loss: 0.2872, Train Acc: 89.33%, Test Loss: 0.2830, Test Acc: 89.32% Epoch 5/10: Train Loss: 0.2521, Train Acc: 90.65%, Test Loss: 0.2747, Test Acc: 90.11% Epoch 6/10: Train Loss: 0.2228, Train Acc: 91.58%, Test Loss: 0.2585, Test Acc: 90.44% Epoch 7/10: Train Loss: 0.1985, Train Acc: 92.61%, Test Loss: 0.2545, Test Acc: 91.10% Epoch 8/10: Train Loss: 0.1767, Train Acc: 93.42%, Test Loss: 0.2654, Test Acc: 90.92% Epoch 9/10: Train Loss: 0.1535, Train Acc: 94.28%, Test Loss: 0.2362, Test Acc: 91.81% Epoch 10/10: Train Loss: 0.1324, Train Acc: 94.98%, Test Loss: 0.2662, Test Acc: 91.24%我们对比三个架构在Fashion-MNIST数据集上的结果,发现测试集的Accuracy,VGG-11表现最好,突破了0.91,AlexNet次之,LeNet最低,这说明使用更深的网络是能够提升图像识别性能的。总结我们介绍了三种经典的卷积神经网络架构:LeNet,AlexNet,VGG。他们的共同思想都是使用卷积层来学习图片的空间信息,提取特征,最后使用全连接层转换到我们要的分类空间。LeNet是首个成功应用在手写数字识别数据集上的深度卷积神经网络,只有2个卷积层、两个池化层和三个全连接层AlexNet在LeNet基础上使用了更多更深的卷积层,在2012年的ImageNet比赛上一战成名,从此引领了深度学习的浪潮VGG在AlexNet的基础上构建了一个非常深的卷积神经网络,通过堆叠多个小尺寸的卷积核和池化层来逐步提取图像特征。它的设计简单一致,具有较好的性能和可迁移性,成为了深度学习研究中的重要里程碑之一。从LeNet到AlexNet再到VGG,网络在不断的变深变大,模型参数也在不断增加,包括现在很多模型都是上亿个参数,这对数据集和硬件都有很高的要求,后续我们再介绍一些能够减少模型参数的方法。
0
0
0
浏览量1950
乌兰推

NiN(Network in Network)和1×1卷积

我们之前介绍了LeNet,AlexNet,VGG。在我们用卷积层提取特征后,全连接层的参数如下可以看出,全连接层的参数很大,很占内存。因此,如果可以不使用全连接层,或者说减少全连接层的个数,可以减少参数,减少过拟合。下面我们来介绍这一章要介绍的内容NiNNiN(Network in Network)1. 1×1卷积网络在架构内容设计方面,其中一个比较有帮助的想法是使用1×1卷积。如下所示也许你会好奇,1×1的卷积能做什么呢?不就是乘以数字么?似乎没有什么用,我们来具体看看它如何工作的。假设一个1×1卷积,这里是数字2,输入一张6×6×1的图片,然后对它做卷积,卷积层大小为1×1×1,结果相当于把这个图片乘以2,所以前三个单元格分别是2、4、6等等。用1×1的过滤器进行卷积,似乎用处不大,只是对输入矩阵乘以某个数字。但这仅仅是对于6×6×1的一个通道的图片来说,1×1卷积效果不佳如果是一张6×6×32的图片,那么使用1×1过滤器进行卷积效果更好。具体来说,1×1卷积所实现的功能是遍历这36个单元格,计算左图中32个数字和过滤器中32个数字的元素积之和,然后应用ReLU非线性函数。这个1×1×32过滤器中的32可以这样理解,一个神经元的输入是32个数字(输入图片中32个通道中的数字),即相同高度和宽度上某一切片上的32个数字,这32个数字具有不同通道,乘以32个权重(将过滤器中的32个数理解为权重),然后应用ReLU非线性函数,在这里输出相应的结果。所以1×1卷积可以从根本上理解为对这32个不同的位置都应用一个全连接层。和传统的CNN在卷积层之后接全连接层相比,全连接层会将特征图展平为一个向量,并进行线性变换。而用1×1卷积核替代全连接层,将空间上的每个像素点作为一组特征进行卷积操作,从而保留了空间结构信息,避免展平为向量,提高了网络的表达能力。此外,当有多个卷积层时,我们可以更改输出通道数,如下图所示。输入图片为4×4×3第一个1×1卷积是增加通道数(通道从3→6)原始图像 (4×4×3) → Conv 1 (6个1×1 ×3 kernel) → Conv1 输出图像 (4×4×6)第二个1×1卷积是减少通道数(通道从6→2)。Conv1输出图片(4×4×6)→Conv 2(2个1×1×6 kernel)→Conv2输出图片(4×4×2)小结:1×1卷积主要作用通道数调整:通过1×1卷积,可以修改特征图的通道数。卷积操作会对每个通道上的像素进行加权求和,因此,如果将卷积核的通道数设置为所需的输出通道数,就可以实现通道数的调整。这样就能够控制特征图的维度,使其适应后续层的输入要求。特征融合:1×1卷积可以用于特征融合,即将多个特征图按通道进行叠加。通过调整卷积核的通道数,将不同通道的特征图相加,从而实现特征的融合。降维操作:由于1×1卷积可以改变特征图的通道数,因此它也被广泛用于降低特征图的维度。通过使用适当数量的1×1卷积核,可以减少特征图的通道数,从而减少计算量和参数数量。非线性映射:尽管1×1卷积没有类似3×3或5×5卷积核的局部感知野,但它仍然引入了非线性映射。由于卷积操作中存在激活函数,1×1卷积能够对特征图进行非线性变换,并增强网络的表达能力。2. NiN架构NiN(Network in Network) 是一种深度卷积神经网络架构,由Min Lin等人在2013年提出。它的设计目标是通过引入多层感知机(MLP)来提高卷积神经网络(CNN)的表达能力。最初的NiN网络是在AlexNet后不久提出的,从中得到了一些启示。 NiN使用窗口形状为11×11、5×5和3×3的卷积层,输出通道数量与AlexNet中的相同。 每个NiN块后有一个最大汇聚层,汇聚窗口形状为3×3,步幅为2。NiN框架的核心思想是在卷积层内嵌套一个小型MLP网络,用于增强特征表达能力。与传统的CNN不同,NiN框架在每个卷积层中使用1×1的卷积核,这样可以引入非线性变换和参数共享,从而提高特征的非线性表示能力。具体而言,NiN框架包含了以下几个关键组件:NiN块:一个NiN块由一个卷积层和两个1×1卷积层构成。其中,第一个卷积层负责提取空间特征,第二个1×1卷积层将通道数降低,第三个1×1卷积层则将通道数增加。这样的设计可以增加网络的非线性表示能力,并且通过1×1卷积层调整通道数可以灵活控制特征图的维度。全局平均池化层:在NiN网络的最后,通过全局平均池化层将特征图的空间维度降为1×1,得到一个通道数等于类别数的特征图。然后,通过Softmax函数进行分类。下面我们用Pytorch来实现基于NiN架构对Fashion-MNIST数据集识别3. Pytorch代码实现1.定义NiN块这里和原始的nin块有两个1×1卷积不同,我这里只使用了1个1×1卷积,因为数据集比较小,所以使用1个1×1卷积层效果更好,并且也大大节省了训练时间。import torch import torch.nn as nn import torch.optim as optim import torchvision import torchvision.transforms as transforms import matplotlib.pyplot as plt def nin_block(in_channels, out_channels, kernel_size, strides, padding): return nn.Sequential( nn.Conv2d(in_channels, out_channels, kernel_size, strides, padding), nn.ReLU(), nn.Conv2d(out_channels, out_channels, kernel_size=1), nn.ReLU()) 2.定义NiN网络net = nn.Sequential( nin_block(1, 96, kernel_size=11, strides=4, padding=0), nn.MaxPool2d(3, stride=2), nin_block(96, 256, kernel_size=5, strides=1, padding=2), nn.MaxPool2d(3, stride=2), nin_block(256, 384, kernel_size=3, strides=1, padding=1), nn.MaxPool2d(3, stride=2), nn.Dropout(0.5), # 标签类别数是10 nin_block(384, 10, kernel_size=3, strides=1, padding=1), nn.AdaptiveAvgPool2d((1, 1)), # 将四维的输出转成二维的输出,其形状为(批量大小,10) nn.Flatten()) 3.加载数据集# 加载Fashion-MNIST数据集 transform = transforms.Compose([ transforms.ToTensor(), transforms.Resize((224,224)), transforms.Normalize((0.5,), (0.5,)) ]) trainset = torchvision.datasets.FashionMNIST(root='./data', train=True, download=True, transform=transform) trainloader = torch.utils.data.DataLoader(trainset, batch_size=64, shuffle=True, num_workers=2) testset = torchvision.datasets.FashionMNIST(root='./data', train=False, download=True, transform=transform) testloader = torch.utils.data.DataLoader(testset, batch_size=64, shuffle=False, num_workers=2) 4.初始化模型# 检查是否有可用的GPU device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = net.to(device) # 定义损失函数和优化器 criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(model.parameters(), lr=0.1) 5.模型训练与评估# 训练模型 num_epochs = 10 train_losses = [] test_losses = [] for epoch in range(num_epochs): train_loss = 0.0 test_loss = 0.0 correct = 0 total = 0 # 训练模型 model.train() for images, labels in trainloader: images, labels = images.to(device), labels.to(device) optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels) loss.backward() optimizer.step() train_loss += loss.item() # 测试模型 model.eval() with torch.no_grad(): for images, labels in testloader: images, labels = images.to(device), labels.to(device) outputs = model(images) loss = criterion(outputs, labels) test_loss += loss.item() _, predicted = torch.max(outputs.data, 1) total += labels.size(0) correct += (predicted == labels).sum().item() avg_train_loss = train_loss / len(trainloader) avg_test_loss = test_loss / len(testloader) train_losses.append(avg_train_loss) test_losses.append(avg_test_loss) print(f"Epoch [{epoch+1}/{num_epochs}], Train Loss: {avg_train_loss:.4f}, Test Loss: {avg_test_loss:.4f}, Acc: {correct/total*100:.2f}%") # 绘制测试误差和训练误差曲线 plt.plot(train_losses, label='Training Loss') plt.plot(test_losses, label='Testing Loss') plt.xlabel('Epoch') plt.ylabel('Loss') plt.legend() plt.show() Epoch [1/10], Train Loss: 2.2827, Test Loss: 2.0189, Acc: 33.98% Epoch [2/10], Train Loss: 1.7984, Test Loss: 1.2083, Acc: 59.43% Epoch [3/10], Train Loss: 1.0804, Test Loss: 0.9443, Acc: 65.87% Epoch [4/10], Train Loss: 1.0075, Test Loss: 0.8990, Acc: 67.74% Epoch [5/10], Train Loss: 0.8120, Test Loss: 0.8054, Acc: 69.70% Epoch [6/10], Train Loss: 0.7379, Test Loss: 0.7040, Acc: 73.27% Epoch [7/10], Train Loss: 0.4918, Test Loss: 0.5636, Acc: 79.59% Epoch [8/10], Train Loss: 0.4344, Test Loss: 0.4079, Acc: 84.71% Epoch [9/10], Train Loss: 0.4012, Test Loss: 0.3962, Acc: 85.51% Epoch [10/10], Train Loss: 0.3833, Test Loss: 0.3757, Acc: 85.74% 结果和AlexNet比,test精确度还要低一些,可能是我们的数据集太小,把batch_size调大一点可能效果会好一些。总结NiN框架的主要优点是:提高了表达能力:引入了MLP结构,增强了网络的非线性表示能力,有助于更好地捕捉复杂的特征。减少参数:使用1×1卷积核和全局平均池化层,减少了网络中的参数数量,降低了过拟合的风险。提高计算效率:由于减少了参数数量,NiN框架相对于传统的CNN具有更高的计算效率。总的来说,NiN框架在许多计算机视觉任务中取得了很好的性能,成为CNN架构设计中的重要思路之一,后续我们要介绍的GoogleNet借用了这种思想。
0
0
0
浏览量193
乌兰推

【深度学习】:《PyTorch入门到项目实战》从0到1实现logistic回归

【深度学习】:《PyTorch入门到项目实战》从0到1实现logistic回归logistic回归虽然名字是回归,但实际上是一个分类算法,主要处理二分类问题,具体理论部分大家可以看我的这篇文章。机器学习算法:分类算法详解基本模型如下:损失函数:其中y=1或0.从损失函数可以看出,如果yyy和H(x)H(x)H(x)很接近,则损失函数越小,下面我们来看看如何使用pytorch实现logistic回归🥝1.创建数据集import torch import torch.nn as nn import torch.nn.functional as F#神经网络内置函数 import torch.optim as optim # 设计随机数,为了结果的可复现性 torch.manual_seed(1) <torch._C.Generator at 0x23225f75f78> x = torch.tensor([[1, 2], [2, 3], [3, 1], [4, 3], [5, 3], [6, 2]],dtype=torch.float) y = torch.tensor([[0], [0], [0], [1], [1], [1]],dtype=torch.float) 考虑以下分类问题:给定每个学生观看讲座和在代码实验室工作的小时数,预测学生是否通过了课程。例如,第一个(索引0)学生看了一个小时的讲座,在实验课上花了两个小时([1,2]),结果课程不及格([0])。🍆2.初始化参数和之前一样,我们初始化两个参数W = torch.zeros((2, 1), requires_grad=True) b = torch.zeros(1, requires_grad=True) 🍇3.计算模型h = 1 / (1 + torch.exp(-(torch.matmul(x,W) + b))) tensor([[0.5000], [0.5000], [0.5000], [0.5000], [0.5000], [0.5000]], grad_fn=<SigmoidBackward0>) 在torch中,我们也可以使用torch.sigmoid()函数得到一样的结果h = torch.sigmoid(torch.matmul(x,W)+b) 🍓4.定义损失函数def loss_fun(y,h): return (-(y * torch.log(h) + (1 - y) * torch.log(1 - h))).mean() 在nn中,包含许多内置函数,其中包含了计算交叉熵函数F.binary_cross_entropy,可以实现与上述代码一样的结果 F.binary_cross_entropy(h, y) tensor(0.6931, grad_fn=<BinaryCrossEntropyBackward0>) 🍅5.梯度下降求解optim中包含了常见的优化算法,包括Adam,SGD等,这里我们还是和之前一样使用==随机梯度下降==,后续会介绍其他的优化算法optimizer = optim.SGD([W, b], lr=0.05) 🍊6.模型训练完整代码''' 生成数据集 ''' x = torch.tensor([[1, 2], [2, 3], [3, 1], [4, 3], [5, 3], [6, 2]],dtype=torch.float) y = torch.tensor([[0], [0], [0], [1], [1], [1]],dtype=torch.float) ''' 初始化参数 ''' W = torch.zeros((2, 1), requires_grad=True) b = torch.zeros(1, requires_grad=True) ''' 训练模型 ''' optimizer = optim.SGD([W, b], lr=0.5) nb_epochs = 1000 for epoch in range(nb_epochs + 1): #计算h h = torch.sigmoid(x.matmul(W) + b) #计算损失函数 cost = -(y * torch.log(h) + (1 - y) * torch.log(1 - h)).mean() # 梯度下降优化 optimizer.zero_grad() cost.backward() optimizer.step() if epoch % 100 == 0: print('Epoch {:4d}/{} Cost: {:.6f}'.format( epoch, nb_epochs, cost.item() )) Epoch 0/1000 Cost: 0.693147 Epoch 100/1000 Cost: 0.232941 Epoch 200/1000 Cost: 0.147042 Epoch 300/1000 Cost: 0.107431 Epoch 400/1000 Cost: 0.084848 Epoch 500/1000 Cost: 0.070247 Epoch 600/1000 Cost: 0.060012 Epoch 700/1000 Cost: 0.052428 Epoch 800/1000 Cost: 0.046575 Epoch 900/1000 Cost: 0.041916 Epoch 1000/1000 Cost: 0.038117 🍒7.评估模型在我们完成模型的训练后,我们想检查我们的模型是否适合训练集。# 首先根据估计的参数结果计算h h = torch.sigmoid(x.matmul(W) + b) print(h) tensor([[0.0033], [0.0791], [0.1106], [0.8929], [0.9880], [0.9968]], grad_fn=<SigmoidBackward0>) # 大于0.5的为True,小于0.5的为False prediction = h >= torch.FloatTensor([0.5]) print(prediction) tensor([[False], [False], [False], [ True], [ True], [ True]]) # 注意在python中0=False,1=True print(prediction) print(y) tensor([[False], [False], [False], [ True], [ True], [ True]]) tensor([[0.], [0.], [0.], [1.], [1.], [1.]]) # 计算预测值和真实值相同的个数 correct_prediction = prediction.float() == y print(correct_prediction) tensor([[True], [True], [True], [True], [True], [True]]) # 计算预测正确的数量占总数量的比例 accuracy = correct_prediction.sum().item() / len(correct_prediction) print('The model has an accuracy of {:2.2f}% for the training set.'.format(accuracy * 100)) The model has an accuracy of 100.00% for the training set. 🥤8.使用nn.Module实现logistic回归上面为了演示logistic回归的具体实现原理,我们是使用一步一步实现的,但是在实际中,往往会使用nn.module或者nn实现,下面是实现logistic的简化代码。''' 定义二元分类器 ''' class BinaryClassifier(nn.Module): def __init__(self): super().__init__() self.linear = nn.Linear(2, 1) self.sigmoid = nn.Sigmoid() def forward(self, x): return self.sigmoid(self.linear(x)) model = BinaryClassifier() ''' 定义随机梯度下降 ''' optimizer = optim.SGD(model.parameters(), lr=0.7) ''' 模型训练 ''' nb_epochs = 100 for epoch in range(nb_epochs + 1): #计算h hypothesis = model(x) # 计算损失函数 cost = F.binary_cross_entropy(hypothesis, y) # 梯度下降 optimizer.zero_grad() cost.backward() optimizer.step() # 输出结果 if epoch % 10 == 0: prediction = hypothesis >= torch.FloatTensor([0.5]) correct_prediction = prediction.float() == y accuracy = correct_prediction.sum().item() / len(correct_prediction) print('Epoch {:4d}/{} Cost: {:.6f} Accuracy {:2.2f}%'.format( epoch, nb_epochs, cost.item(), accuracy * 100, )) Epoch 0/100 Cost: 0.734527 Accuracy 50.00% Epoch 10/100 Cost: 0.446570 Accuracy 66.67% Epoch 20/100 Cost: 0.448868 Accuracy 66.67% Epoch 30/100 Cost: 0.375859 Accuracy 83.33% Epoch 40/100 Cost: 0.318583 Accuracy 83.33% Epoch 50/100 Cost: 0.268096 Accuracy 83.33% Epoch 60/100 Cost: 0.222295 Accuracy 100.00% Epoch 70/100 Cost: 0.183465 Accuracy 100.00% Epoch 80/100 Cost: 0.158036 Accuracy 100.00% Epoch 90/100 Cost: 0.144541 Accuracy 100.00% Epoch 100/100 Cost: 0.134652 Accuracy 100.00%
0
0
0
浏览量1179
乌兰推

【深度学习】:《PyTorch入门到项目实战》第三天:简洁代码实现线性神经网络(附代码)

🌺1.生成数据集import numpy as np import torch from torch.utils import data 接下来我们定义真实的w和b,以及生成模拟数据集,这一步和之前讲的一样def synthetic_data(w, b, num_examples): X = torch.normal(0, 1, (num_examples, len(w))) y = torch.matmul(X, w) + b y += torch.normal(0, 0.01, y.shape) return X, y.reshape((-1, 1)) true_w = torch.tensor([2, -3.4]) true_b = 4.2 features, labels = synthetic_data(true_w, true_b, 1000) 🌻2.读取数据集下面有一点不同的是我们通过data.TensorDataset把数据放在Tensor结构中,如何通过batch_size来设置每次抽取几个样本def load_array(data_arrays,batch_size,is_train=True): # TensorDataset:将数据对应成Tensor列表 data_set = data.TensorDataset(*data_arrays) return data.DataLoader(data_set,batch_size,shuffle=is_train)#将数据加载读取出来,batch_size定义个数 batch_size = 10 data_iter = load_array((features,labels),batch_size) next(iter(data_iter)) [tensor([[-0.0384, 1.1566], [-0.9023, -0.6922], [-0.0652, 1.1757], [-0.8569, -1.0172], [ 1.3489, -0.6855], [ 0.1463, 0.1577], [ 0.1615, -2.1549], [-0.0533, -0.3301], [-0.9913, 0.2226], [ 0.1432, -0.9537]]), tensor([[ 0.1836], [ 4.7540], [ 0.0802], [ 5.9541], [ 9.2256], [ 3.9620], [11.8700], [ 5.2242], [ 1.4718], [ 7.7181]])] 🌼3.线性模型搭建这里我们通过nn搭建一个线性的神经网络from torch import nn 这里我们使用Sequential类来接收线性层,这里我们只有一个线性神经网络层,其实可以不设置,但是在后续我们介绍的其他算法中,往往都是多层的,因此我们可以把这个当做一个标准化流程。它的作用是将不同层串在一起,首先将数据传入到第一层,然后将第一层的输出传入到第二层作为输入,以此类推net = nn.Sequential(nn.Linear(2,1))#第一个参数表示输入特征的纬度,第二个参数表示输出层的纬度 🌷4.初始化参数在定义net之后,我们需要做的就是定义我们要估计的参数。还是和之前类似,这里我们也需要定义两个参数一个是weight相当于之前的w,一个是bias相当于之前的bnet[0].weight.data.normal_(0, 0.01)#第一层的weight初始化 net[0].bias.data.fill_(0)#第一层的bias初始化 tensor([0.]) 🌱5. 定义损失函数这里我们使用均方误差MSE来作为我们的损失函数loss = nn.MSELoss() 🌲6. 选择优化方法这里我们使用随机梯度下降进行优化。从而得到我们的参数.需要传入两个参数,一个是==待估计参数==,另一个是==学习率==。我在这里设置为0.03trainer = torch.optim.SGD(net.parameters(), lr=0.03) num_epochs = 3 for epoch in range(num_epochs): for X, y in data_iter: l = loss(net(X) ,y) trainer.zero_grad() l.backward() trainer.step()#更新参数 l = loss(net(features), labels) print(f'epoch {epoch + 1}, loss {l:f}') epoch 1, loss 0.000102 epoch 2, loss 0.000103 epoch 3, loss 0.000104 🌴7.完整代码# 导入相关库 import numpy as np import torch from torch.utils import data from torch import nn ''' 定义模拟数据集函数 ''' def synthetic_data(w, b, num_examples): X = torch.normal(0, 1, (num_examples, len(w)))#生成标准正态分布 y = torch.matmul(X, w) + b#计算y y += torch.normal(0, 0.01, y.shape) return X, y.reshape((-1, 1)) ''' 生成数据集 ''' true_w = torch.tensor([2, -3.4])#定义w true_b = 4.2#定义b features, labels = synthetic_data(true_w, true_b, 1000)#生成模拟数据集 ''' 加载数据集 ''' def load_array(data_arrays,batch_size,is_train=True): # TensorDataset:将数据对应成Tensor列表 data_set = data.TensorDataset(*data_arrays) return data.DataLoader(data_set,batch_size,shuffle=is_train)#将数据加载读取出来,batch_size定义个数 batch_size = 10 data_iter = load_array((features,labels),batch_size) ''' 创建线性神经网络 ''' net = nn.Sequential(nn.Linear(2,1))#第一个参数表示输入特征的纬度,第二个参数表示输出层的纬度 net[0].weight.data.normal_(0, 0.01)#第一层的weight初始化 net[0].bias.data.fill_(0)#第一层的bias初始化 ''' 定义损失函数MSE ''' loss = nn.MSELoss() ''' 创建SGD优化方法 ''' trainer = torch.optim.SGD(net.parameters(), lr=0.03)#创建SGD优化训练器 ''' 正式训练 ''' num_epochs = 3#迭代次数 for epoch in range(num_epochs): for X, y in data_iter: l = loss(net(X) ,y)#计算损失函数 trainer.zero_grad() l.backward() trainer.step()#更新参数 l = loss(net(features), labels)#计算最终的loss print(f'epoch {epoch + 1}, loss {l:f}') epoch 1, loss 0.000240 epoch 2, loss 0.000099 epoch 3, loss 0.000100
0
0
0
浏览量563
乌兰推

GoogLeNet(附Pytorch源码)

GoogleNet(Inception v1)是由Google团队在2014年提出的深度卷积神经网络架构。它是为解决图像分类任务而设计的,并在ImageNet图像分类挑战赛中取得了很好的成绩。与VGGNet、LeNet、AlexNet有较大不同。在之前我们介绍的架构中VGG大量使用了3×3卷积,AlexNet使用了5×5,而NiN使用了1×1。因此,在构建卷积层时,我们要决定过滤器的大小究竟是1×1,3×3还是5×5,或者要不要添加池化层。而GoogleNet网络的想法就是**我全都要!**GoogleNet最显著的特点是采用了一系列并行连接的Inception模块。原文作者提到,“Inception”这个名字的想法来自于电影《盗梦空间》一个著名的网络梗:WE NEED TO GO DEEPER。虽然网络架构因此变得更加复杂,但网络表现却非常好,下面我们来看一下GoogleNet具体组成架构:1×1卷积1×1 卷积由 NiN引入。1×1卷积与ReLU一起使用。因此,NiN最初使用它来引入更多的非线性,以提高网络的表示能力,因为NiN的作者认为数据是非线性形式的。在GoogLeNet中,1×1卷积被用作降维模块,以减少计算量。通过减少计算瓶颈,可以增加深度和宽度。我举一个简单的例子来说明这一点。假设我们需要执行 5×5 卷积而不使用 1×1 卷积,如下所示:运算次数 = (14×14×48)×(5×5×480) = 112.9M使用 1×1 卷积:1×1的运算次数 = (14×14×16)×(1×1×480) = 1.5M5×5的运算次数 = (14×14×48)×(5×5×16) = 3.8M总操作次数 = 1.5M + 3.8M = 5.3M,这比 112.9M 小得多!!!事实上,上面的例子是在inception时 5×5 卷积的计算。(我们可以认为,当降维时,实际上我们正在以非线性的方式进行从高维到低维的映射。相反,对于PCA,它执行线性降维。)因此,与没有 1×1 卷积的情况相比,我们可以在不增加操作数量的情况下构建 inception 模块。1×1卷积可以帮助减小模型大小,这在某种程度上也有助于减少过拟合问题2. Inception模块与之前介绍的lexNet 和 VGGNet,每层的 conv 大小都是固定的相比。Inception模块将输入特征图分别进行多个不同尺寸的卷积和池化操作,并将它们的结果进行拼接。这样可以同时捕捉到不同尺度上的特征,从而提高了特征的表达能力。具体而言,Inception模块包含了1×1、3×3和5×5的卷积层,以及1×1卷积和3×3最大池化层。通过使用不同尺寸的卷积和池化操作,网络能够有效地捕捉到局部和全局的特征。3. 全局平均池化层:在网络的最后,GoogleNet使用全局平均池化层将特征图的大小降为1×1。这种操作可以将特征图中每个通道的特征合并为一个标量值,从而减少参数数量,并且有助于提取更加全局的特征。之前的网络以全连接(FC)层用于网络末端,所有输入都连接到每个输出。上面的权重(连接)数量 = 7×7×1024×1024 = 51.3M在GoogLeNet中,全局平均池化在网络末端使用,通过对每个特征图从7×7到1×1进行平均权重数量 = 04.训练辅助分类器在模型中间的一部分有softmax分支,它们仅用于训练。这些分支是辅助分类器,包括:5×5 平均池化(步幅 3)1×1 卷积(128 个过滤器)1024 FC1000FCSoftmax5.基本架构了解了上面介绍的基本单元之后,我们就可以谈谈整体的网络架构了。总共有22层!与之前的AlexNet、LeNet和VGGNet相比,它已经是一个非常深的模型了。 而且我们可以看到有很多inception模块连接在一起,甚至可以更深入。以下是各层参数的详细信息。我们其实可以扩展1×1卷积的例子来自己计算运算次数。6.Pytorch代码实现import warnings warnings.filterwarnings('ignore') # 导入相关库 import torch import torch.nn as nn import torch.optim as optim from torch.nn import functional as F import torchvision import torchvision.transforms as transforms import matplotlib.pyplot as plt # inception块 class Inception(nn.Module): # c1--c4是每条路径的输出通道数 def __init__(self, in_channels, c1, c2, c3, c4, **kwargs): super(Inception, self).__init__(**kwargs) # 线路1,单1x1卷积层 self.p1_1 = nn.Conv2d(in_channels, c1, kernel_size=1) # 线路2,1x1卷积层后接3x3卷积层 self.p2_1 = nn.Conv2d(in_channels, c2[0], kernel_size=1) self.p2_2 = nn.Conv2d(c2[0], c2[1], kernel_size=3, padding=1) # 线路3,1x1卷积层后接5x5卷积层 self.p3_1 = nn.Conv2d(in_channels, c3[0], kernel_size=1) self.p3_2 = nn.Conv2d(c3[0], c3[1], kernel_size=5, padding=2) # 线路4,3x3最大汇聚层后接1x1卷积层 self.p4_1 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1) self.p4_2 = nn.Conv2d(in_channels, c4, kernel_size=1) def forward(self, x): p1 = F.relu(self.p1_1(x)) p2 = F.relu(self.p2_2(F.relu(self.p2_1(x)))) p3 = F.relu(self.p3_2(F.relu(self.p3_1(x)))) p4 = F.relu(self.p4_2(self.p4_1(x))) # 在通道维度上连结输出 return torch.cat((p1, p2, p3, p4), dim=1) # 构建google-Net b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3), nn.ReLU(), nn.MaxPool2d(kernel_size=3, stride=2, padding=1)) b2 = nn.Sequential(nn.Conv2d(64, 64, kernel_size=1), nn.ReLU(), nn.Conv2d(64, 192, kernel_size=3, padding=1), nn.ReLU(), nn.MaxPool2d(kernel_size=3, stride=2, padding=1)) b3 = nn.Sequential(Inception(192, 64, (96, 128), (16, 32), 32), Inception(256, 128, (128, 192), (32, 96), 64), nn.MaxPool2d(kernel_size=3, stride=2, padding=1)) b4 = nn.Sequential(Inception(480, 192, (96, 208), (16, 48), 64), Inception(512, 160, (112, 224), (24, 64), 64), Inception(512, 128, (128, 256), (24, 64), 64), Inception(512, 112, (144, 288), (32, 64), 64), Inception(528, 256, (160, 320), (32, 128), 128), nn.MaxPool2d(kernel_size=3, stride=2, padding=1)) b5 = nn.Sequential(Inception(832, 256, (160, 320), (32, 128), 128), Inception(832, 384, (192, 384), (48, 128), 128), nn.AdaptiveAvgPool2d((1,1)), nn.Flatten()) net = nn.Sequential(b1, b2, b3, b4, b5, nn.Linear(1024, 10)) # Xavier初始化:防止梯度爆炸,这是CNN常用的操作,特别对于GoogleNet这种已经算比较深的网络而言,特别有效,之前我们也介绍过他的具体公式。 def init_weights(m): if type(m) == nn.Linear or type(m) == nn.Conv2d: #对全连接层和卷积层初始化 nn.init.xavier_uniform_(m.weight) net.apply(init_weights) # 检查是否有可用的GPU device = torch.device('cuda'if torch.cuda.is_available() else 'cpu') model = net.to(device) # 定义损失函数和优化器 criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(model.parameters(), lr=0.1) # 加载Fashion-MNIST数据集 transform = transforms.Compose([ transforms.ToTensor(), transforms.Resize((96,96)), transforms.Normalize((0.5,), (0.5,)) ]) trainset = torchvision.datasets.FashionMNIST(root='./data', train=True, download=True, transform=transform) trainloader = torch.utils.data.DataLoader(trainset, batch_size=128, shuffle=True, num_workers=2) testset = torchvision.datasets.FashionMNIST(root='./data', train=False, download=True, transform=transform) testloader = torch.utils.data.DataLoader(testset, batch_size=128, shuffle=False, num_workers=2) # 训练模型 num_epochs = 10 train_losses = [] test_losses = [] train_accs = [] test_accs = [] for epoch in range(num_epochs): train_loss = 0.0 train_total = 0 train_correct = 0 model.train() for images, labels in trainloader: images, labels = images.to(device), labels.to(device) optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels) loss.backward() optimizer.step() train_loss += loss.item() _, predicted = torch.max(outputs.data, 1) train_total += labels.size(0) train_correct += (predicted == labels).sum().item() train_loss /= len(trainloader) train_accuracy = 100*train_correct / train_total train_losses.append(train_loss) train_accs.append(train_accuracy) test_loss = 0.0 test_total = 0 test_correct = 0 model.eval() with torch.no_grad(): for images, labels in testloader: images, labels = images.to(device), labels.to(device) outputs = model(images) loss = criterion(outputs, labels) test_loss += loss.item() _, predicted = torch.max(outputs.data, 1) test_total += labels.size(0) test_correct += (predicted == labels).sum().item() test_loss /= len(testloader) test_accuracy = 100*test_correct / test_total test_losses.append(test_loss) test_accs.append(test_accuracy) print(f"Epoch {epoch+1}/{num_epochs}: Train Loss: {train_loss:.4f}, Train Acc: {train_accuracy:.2f}%, Test Loss: {test_loss:.4f}, Test Acc: {test_accuracy:.2f}%") # 绘制训练误差和测试误差曲线 plt.figure(figsize=(10, 5)) plt.plot(range(1, num_epochs+1), train_losses, label='Train Loss') plt.plot(range(1, num_epochs+1), test_losses, label='Test Loss') plt.xlabel('Epoch') plt.ylabel('Loss') plt.title('Training and Testing Loss') plt.legend() plt.show() # 绘制训练准确率和测试准确率曲线 plt.figure(figsize=(10, 5)) plt.plot(range(1, num_epochs+1), train_accs, label='Train Acc') plt.plot(range(1, num_epochs+1), test_accs, label='Test Acc') plt.xlabel('Epoch') plt.ylabel('Accuracy') plt.title('Training and Testing Accuracy') plt.legend() plt.show() Epoch 1/10: Train Loss: 2.0245, Train Acc: 0.24, Test Loss: 1.2393, Test Acc: 0.50 Epoch 2/10: Train Loss: 0.8974, Train Acc: 0.67, Test Loss: 1.9615, Test Acc: 0.29 Epoch 3/10: Train Loss: 0.6790, Train Acc: 0.75, Test Loss: 0.4919, Test Acc: 0.81 Epoch 4/10: Train Loss: 0.4275, Train Acc: 0.84, Test Loss: 0.4063, Test Acc: 0.85 Epoch 5/10: Train Loss: 0.3615, Train Acc: 0.86, Test Loss: 0.3978, Test Acc: 0.84 Epoch 6/10: Train Loss: 0.3253, Train Acc: 0.88, Test Loss: 0.3284, Test Acc: 0.88 Epoch 7/10: Train Loss: 0.2964, Train Acc: 0.89, Test Loss: 0.3079, Test Acc: 0.89 Epoch 8/10: Train Loss: 0.2726, Train Acc: 0.90, Test Loss: 0.2953, Test Acc: 0.89 Epoch 9/10: Train Loss: 0.2540, Train Acc: 0.90, Test Loss: 0.3061, Test Acc: 0.88 Epoch 10/10: Train Loss: 0.2380, Train Acc: 0.91, Test Loss: 0.2799, Test Acc: 0.90 从结果可以看出,GoogLeNet的精确度超过了0.9,比之前的AlexNet更好。总结GoogleNet的是通过Inception模块的并行连接和多尺度的卷积和池化操作来提高特征的表示能力。它相对于传统的深度网络具有更高的计算效率和更强大的特征学习能力。基本思想是Inception网络不需要人为决定使用哪个过滤器或者是否需要池化,而是由网络自行确定这些参数,你可以给网络添加这些参数的所有可能值,然后把这些输出连接起来,让网络自己学习它需要什么样的参数,采用哪些过滤器组合。简单来说,这些参数都是试出来的,Google太有钱了。GoogleNet的成功开创了一系列基于Inception架构的后续版本(如Inception v2、v3等),为深度学习在计算机视觉任务中的广泛应用奠定了基础。
0
0
0
浏览量2015
乌兰推

【深度学习】:《100天一起学习PyTorch》模型评估和选择(上):欠拟合和过拟合

1.基本概念机器学习的任务是发现一种泛化的模式,通过训练集发现总体的规律,从而在未知的数据集上也能展现较好的精度。但是如何判断我们的模型不是单纯的记住了数据,而是真的发现了一种规律呢?因为,我们往往只能从有限样本集训练模型,当收集更多的数据时,会发现这些数据的预测结果和之前的关系完全不同。下面我们介绍一些机器学习评估模型的一些基本概念。1.1训练误差和泛化误差训练误差:模型在训练集上的误差泛化误差:模型误差的期望在现实情况,我们永远不能准确计算出泛化误差,因此, 在实际中,我们只能通过将模型应用于一个独立的测试集来估计泛化误差。1.2训练集、验证集和测试集训练集:用于训练模型,得到模型参数验证集:用于选择模型,调整超参数测试集:用于评估模型用一个很形象的比喻就是:训练集相当于平时的练习,验证集相当于平时的小测验,测试集相当于期末考试。首先要保证平时练习的练习正确率较高,才能在期末考试中拿到较好的成绩。但是如果作弊看了练习题的答案,那么这个时候平时的练习会有较高的正确率,但是期末考试没有答案抄就拿不到好成绩了,这时就需要平时的小测验来验证一下你的学习成果,来避免你是因为偷看了练习答案从而有有了较高的正确率。在训练数据时,我不希望用到测试集的数据,因为这样的话测试集得到的评估结果是很容易过拟合的。因此我们需要将数据集分为训练集、验证集和测试集,但是在实际应用是,验证集和测试集往往区分的不是很清楚。因此,很多时候,在实际中只设置了训练集和验证集。因此,我们在后续主要关注验证集的误差。1.3 交叉验证我们讨论了训练误差和验证误差。我们常常用交叉验证的方法来计算验证误差:1.4模型复杂度在得到训练的模型后,计算其训练误差,验证误差。往往会出现两种情况,一种是过拟合,一种是欠拟合欠拟合(underfitting):欠拟合是指模型在训练集上表现的也不好,模型不能很好的拟合训练集过拟合(overfitting):模型在训练集上表现很好,但是在测试集上表现的较差正则化(regularization):正则化可以用于处理过拟合问题当模型出现欠拟合时候,我们可以考虑使用更复杂的模型来进行训练,当模型过拟合时,需要减少模型的复杂度。具体关系如下图所示:一般来说,当数据集很多时,使用较复杂的模型;当数据集较少时,使用交简单的模型。下面我们以多项式回归为例来具体看看这些指标情况2. 多项式回归经过上述一些概念的介绍,下面通过一个多项式的具体例子来看一下,首先多项式回归定义如下:import math import numpy as np import torch from torch import nn from torch.utils import data from IPython import display 生成一个模拟数据集,其真实的关系是三次多项式回归max_degree = 20 # 多项式的最大阶数 n_train, n_test = 100, 100 # 训练和测试数据集大小 true_w = np.zeros(max_degree) # 设置w true_w[0:4] = np.array([5.1, 1.2, -3.1, 5.1]) features = np.random.normal(size=(n_train + n_test, 1)) np.random.shuffle(features) poly_features = np.power(features, np.arange(max_degree).reshape(1, -1)) for i in range(max_degree): poly_features[:, i] /= math.gamma(i + 1) # labels的维度:(n_train+n_test,) labels = np.dot(poly_features, true_w) labels += np.random.normal(scale=0.1, size=labels.shape) 下面将多维数组转换为张量(tensor)true_w, features, poly_features, labels = [torch.tensor(x, dtype= torch.float32) for x in [true_w, features, poly_features, labels]] 接下来需要先定义一些基本的函数,大家可以直接下载d2l库导入,在沐神的教材上都有,但是有的时候安装d2l报错,因此如果大家不想安装d2l的话,可以参考一下下面这些函数,大家也可以自己将这些函数写入自己的包中方便导入。# 定义数据迭代器函数 def load_array(data_arrays, batch_size, is_train=True): """构造一个PyTorch数据迭代器""" dataset = data.TensorDataset(*data_arrays)#将数据转换为tensor return data.DataLoader(dataset, batch_size, shuffle=is_train) # 定义一个类来接收变量 class Accumulator: #@save """在n个变量上累计""" def __init__(self, n): self.data = [0.0] * n def add(self, *args): self.data = [a + float(b) for a, b in zip(self.data, args)] def reset(self): self.data = [0.0] * len(self.data) def __getitem__(self, idx): return self.data[idx] # 定义准确率函数 def accuracy(y_hat, y): #@save """计算预测正确的数量""" if len(y_hat.shape) > 1 and y_hat.shape[1] > 1: y_hat = y_hat.argmax(axis=1) cmp = y_hat.type(y.dtype) == y return float(cmp.type(y.dtype).sum()) # 计算误差函数 def evaluate_loss(net, data_iter, loss): #@save """评估给定数据集上模型的损失""" metric = Accumulator(2) # 损失的总和,样本数量 for X, y in data_iter: out = net(X) y = y.reshape(out.shape) l = loss(out, y) metric.add(l.sum(), l.numel()) return metric[0] / metric[1] # 训练函数 def train_epoch(net, train_iter,loss,updater): """三个变量,训练损失,训练准确度,样本数""" net.train() metric = Accumulator(3) for X,y in train_iter: y_hat = net(X) l = loss(y_hat,y) if isinstance(updater, torch.optim.Optimizer):#如果是pytorch内置优化器 updater.zero_grad() l.mean().backward() updater.step() else: """自己定义的优化器""" l.sum().backward() updater(X.shape[0]) metric.add(float(l.sum()),accuracy(y_hat,y),y.numel()) return metric[0]/metric[2], metric[1]/metric[2] # 定义坐标轴函数 def set_axes(axes, xlabel, ylabel, xlim, ylim, xscale, yscale, legend): """设置matplotlib的轴""" axes.set_xlabel(xlabel) axes.set_ylabel(ylabel) axes.set_xscale(xscale) axes.set_yscale(yscale) axes.set_xlim(xlim) axes.set_ylim(ylim) if legend: axes.legend(legend) axes.grid() # 定义保存函数 def use_svg_display(): #@save """使用svg格式在Jupyter中显示绘图""" display.set_matplotlib_formats('svg') # 定义一个动画绘制类 class Animator: #@save """在动画中绘制数据""" def __init__(self, xlabel=None, ylabel=None, legend=None, xlim=None, ylim=None, xscale='linear', yscale='linear', fmts=('-', 'm--', 'g-.', 'r:'), nrows=1, ncols=1, figsize=(3.5, 2.5)): # 增量地绘制多条线 if legend is None: legend = [] plt.show() self.fig, self.axes = plt.subplots(nrows, ncols, figsize=figsize) if nrows * ncols == 1: self.axes = [self.axes, ] # 使用lambda函数捕获参数 self.config_axes = lambda: set_axes( self.axes[0], xlabel, ylabel, xlim, ylim, xscale, yscale, legend) self.X, self.Y, self.fmts = None, None, fmts def add(self, x, y): # 向图表中添加多个数据点 if not hasattr(y, "__len__"): y = [y] n = len(y) if not hasattr(x, "__len__"): x = [x] * n if not self.X: self.X = [[] for _ in range(n)] if not self.Y: self.Y = [[] for _ in range(n)] for i, (a, b) in enumerate(zip(x, y)): if a is not None and b is not None: self.X[i].append(a) self.Y[i].append(b) self.axes[0].cla() for x, y, fmt in zip(self.X, self.Y, self.fmts): self.axes[0].plot(x, y, fmt) self.config_axes() display.display(self.fig) display.clear_output(wait=True) # 定义训练函数 def train(train_features, test_features, train_labels, test_labels, num_epochs=400): loss = nn.MSELoss(reduction='none')#设置损失函数为MSE input_shape = train_features.shape[-1] # 不设置偏差,因为我们已经在多项式中已经设置好了 net = nn.Sequential(nn.Linear(input_shape, 1, bias=False))#定义线性神经网络 batch_size = min(10, train_labels.shape[0])#确定batch train_iter = load_array((train_features, train_labels.reshape(-1,1)), batch_size)#训练集 test_iter = load_array((test_features, test_labels.reshape(-1,1)) ,batch_size)#测试集 trainer = torch.optim.SGD(net.parameters(), lr=0.01)#训练集,使用SGD训练模型 animator = Animator(xlabel='epoch', ylabel='loss', yscale='log', xlim=[1, num_epochs], ylim=[1e-3, 1e2], legend=['train', 'test'])#图形相关设置 for epoch in range(num_epochs): train_epoch(net, train_iter, loss, trainer) if epoch == 0 or (epoch + 1) % 20 == 0: animator.add(epoch + 1, (evaluate_loss(net, train_iter, loss),#计算训练误差并绘制 evaluate_loss(net, test_iter, loss)))#计算测试误差并绘制 print('weight:', net[0].weight.data.numpy()) 2.1 三次多项式回归(正常拟合)因为我们生成的数据集是3次多项式回归得到的,因此使用三次多项式回归拟合结果会很精确train(poly_features[:n_train, :4], poly_features[n_train:, :4], labels[:n_train], labels[n_train:]) weight: [[ 5.1068187 1.2157811 -3.1099443 5.064199 ]] 可以看出,随着训练次数的增加,训练误差和验证误差都不断下降到小于0.01,并且验证误差和验证误差基本一致2.2 一元线性回归(underfitting)下面我们使用一元线性回归来拟合数据,由于我们知道真实的数据集是三次关系的,此时使用一元线性回归无法进行精确拟合,会导致模型的bias较大,训练误差和验证误差都很大# 从多项式特征中选择前2个维度,即1和x train(poly_features[:n_train, :2], poly_features[n_train:, :2], labels[:n_train], labels[n_train:]) weight: [[3.8188436 3.0646155]] 从上图可以看出,和我们预计得到的结果一致,由于模型太简单,连训练集上也不能很好的拟合,导致训练误差和验证误差都很大2.3 10次多项式(过拟合)下面我们使用10次多项式来进行拟合,由于模型的复杂度太高,会导致模型出现过拟合,验证集上的误差会随着训练次数增加会先下降再上升# 从多项式特征中选取所有维度 train(poly_features[:n_train, :11], poly_features[n_train:, :11], labels[:n_train], labels[n_train:], num_epochs=500) weight: [[ 5.0872297 1.2546227 -2.9732502 4.719495 -0.47507587 1.4278368 -0.05434499 0.30877623 0.28959352 0.18821514 0.06768304]] 从上图可以看出,和我们预计的一致,验证误差先减小后增大,如果我们提前结束训练的话,能够得到还不错的结果,这种后续中会介绍
0
0
0
浏览量792
乌兰推

【深度学习】: PyTorch从零实现线性回归

1.线性回归基本概念==线性回归==是机器学习中非常常用的模型之一,特别在研究定量数据的问题中,它能分析变量之间的关系,并给出很好的解释。此外,它还是新方法的一个良好起点:许多有趣的统计学习方法可以被视为线性回归的推广或扩展。例如Lasso回归,岭回归,logistic regression,softmax回归。具体理论介绍部分大家可以看我这篇文章:统计学习方法之线性回归详解简单线性回归模型具体形式可以如下表示:写成向量的形式:在我们拿到一堆数据之后,我们要做就是找到最好的参数W,bW,bW,b,如何找到最好的参数呢?在那之前我们介绍一下==损失函数==和==梯度下降==2.损失函数损失函数是衡量一个模型拟合的重要指标,表示实际值和拟合值之间的差距,在线性回归中,损失函数也被称作==平方误差函数==。我们之所以要求出误差的平方和,是因为一般我们认为误差是非负的,而误差绝对值在求导时不太便利,而误差平方损失函数,对于大多数问题,特别是回归问题,都是一个合理的选择。具体定义如下:我们的目标便是选择出可以使得损失函数能够最小的模型参数,因为线性回归是一个很简单的问题,所有大部分情况下都存在解析解,对L求梯度为0的点。为了方便表示,这里我将bbb也放入到WWW中,则有:3.梯度下降==梯度下降==是一种==优化算法==,后续还会介绍一些其他的优化方法,例如Adam,SGD等。本章暂时用梯度下降来计算参数。梯度下降背后的思想是:开始时我们随机选择一个参数的组合,计算损失函数,然后我们寻找下一个能让损失函数值下降最多的参数组合。具体公式如下:1.初始化一组参数值2.在负梯度方向不断更新参数,其中η\etaη是==学习率==,是一个超参数,它决定了我们沿着能让损失函数下降程度最大的方向步长多大,在梯度下降中,我们每一次都同时让所有的参数减去学习速率乘以损失函数的导数。需要我们提前给定。我们持续这么做直到得到一个==局部最小值==(local minimum),因为我们并没有尝试完所有的参数组合,所以不能确定我们得到的局部最小值是否便是==全局最小值==(global minimum),选择不同的初始参数组合,可能会找到不同的局部最小值。此时如何设置合适的η\etaη值是需要我们考虑的,如果η\etaη太大,则可能到不了最低点,导致无法收敛,如果η\etaη太小,那收敛过程太慢,这些细节问题在之后再讨论,接下来我们来看看如何实现一个简单的线性回归模型。4.Pytorch实现线性回归导入相关库import random import torch import numpy as np import matplotlib.pyplot as plt %matplotlib inline 4.1 生成数据集为了方便举例,这里使用模拟数据集进行展示。假设样本来自标准正态分布,每个样本有两个特征,我们生成1000个数据集,w=[1,−1]Tw=[1,-1]^Tw=[1,−1]T,b=2b=2b=2,ϵ\epsilonϵ是一个均值为0,标准差为0.1的正态分布def simulation_data(w,b,n): X = torch.normal(0,1,(n,len(w)))#生成标准正态分布 y = torch.matmul(X, w)+b#计算回归拟合值 y += torch.normal(0,0.1,y.shape) #加上随机扰动项 return X,y.reshape((-1,1)) true_w = torch.tensor([-1,1],dtype=torch.float32) true_b = 2 features, target = simulation_data(true_w,true_b,1000) 此时已经生成好了模拟数据集,下面我们绘制图形观察一下plt.scatter(features[:,(1)].detach().numpy(),target.detach().numpy(),2) 从上图我可以看出,y和一个特征之间的关系呈现明显的线性关系。4.2 初始化参数下面我们开始初始化我们要求的参数,通常将www设置为均值为0的正态分布,bbb设置为0向量w = torch.normal(0,0.1,(2,1),requires_grad=True) b = torch.zeros(1,requires_grad=True) 4.3 定义回归模型初始化参数之后,我们下一步就是开始定义我们的线性回归模型def reg(X,w,b): return torch.matmul(X,w)+b 4.4 计算损失函数def loss_fun(y_hat,y): return(y_hat-y)**2/2/len(y) 4.5 使用梯度下降求解参数def gd(params,n): with torch.no_grad():#在外面不需要求解梯度,只有参数更新的时候求参数 for param in params: param -= n*param.grad#进行梯度下降 param.grad.zero_()#重新将梯度设置为0,这样就不会受到上一次影响 n = 0.01#学习率 num_epochs = 3#训练次数 net = reg loss = loss_fun X = features y = target for epoch in range(num_epochs): l = loss(reg(X,w,b),y)#计算损失函数 l.sum().backward()#反向传播求梯度 gd([w,b],n)#更新参数 with torch.no_grad(): train = loss(net(features,w,b),target) print(f'epoch{epoch+1},loss:{float(train.mean()):f}') epoch1,loss:0.001797 epoch2,loss:0.001763 epoch3,loss:0.001729 由于本例中是模拟数据集,我们知道真实的参数,因此可以计算出参数估计的误差print(f'w的误差:{true_w-w.reshape(true_w.shape)}') print(f'b的误差:{true_b-b}') w的误差:tensor([-0.8461, 0.8311], grad_fn=<SubBackward0>) b的误差:tensor([1.4857], grad_fn=<RsubBackward1>)
0
0
0
浏览量2014
乌兰推

残差网络(ResNets)(Residual Networks (ResNet))

一、引言经过我们之前的介绍,我们发现对卷积神经网络而言,“越深越好”,这是有道理的,因为这样模型的能力应该更强(它们适应任何空间的灵活性都会提高,因为它们有更大的参数空间可以探索)。然而,很多实验发现,在一定深度之后,性能就会下降。 这是 VGG 的瓶颈之一,因为当神经网络中使用特定激活函数的层数越多,损失函数的梯度就会趋近于零,导致梯度消失和梯度爆炸问题,从而使网络难以训练。最简单的解决方案是使用其他激活函数,例如 ReLU,它不会导致导数很小。而残差神经网络(ResNet) 是另一种有效的解决方案。二、ResNet介绍ResNet(Residual Network) 通过使用残差块(Residual Block)来解决深度神经网络中的梯度消失和梯度爆炸问题。ResNet架构在ImageNet图像分类竞赛中取得了很好的成绩,并且在许多计算机视觉任务中都得到广泛应用。它的核心思想是引入了跳跃连接(skip connection),允许信息直接在网络中跳过一层或多层。这种跳跃连接使得梯度可以更快地向后传播,从而解决了深度网络中的梯度问题。如下图所示,残差连接直接将块开头的值 x 添加到块的末尾 (F(x)+x)。这种残差连接不会通过“压缩”导数的激活函数,从而导致块的整体导数更高。因此,我们可以利用跳跃连接能够构建深度网络的ResNets,有时深度能够超过100层三、ResNet架构ResNets是由残差块(Residual block)构建的,首先我解释一下什么是残差块,最常见的残差块构成方式如下:在每个残差块中,残差层的输入会先通过卷积层进行特征提取,并与原始输入进行相加操作,然后再经过ReLU激活函数处理。这种结构使得网络可以学习到原始输入上的残差,进而提升了模型的性能。根据网络的深度,可以开发出不同类型的 ResNet,如 ResNet-50 或 ResNet-152。ResNet 末尾的数字表示网络的层数或网络的深度。我们可以使用 ResNet 的基本构件设计任意深度的 ResNet。ResNet 也可以看成是VGG 架构的升级版,它们之间的区别在于 ResNet 中使用了跳转连接。下图给出了VGG-19和ResNet-34的架构图:ResNet和VGG有很多相似之处,都大量的使用了3×3卷积,而ResNet在最后使用了全局平均池化层,这和我们之前介绍的GoogleNet一样。ResNet的核心还是引入了残差块,每个残差块包含两个或三个3×3卷积层,其中至少有一个卷积层的输出与输入进行跳跃连接(skip connection)。这种跳跃连接允许信息直接在深层网络中跨层传递,避免了梯度消失和信息丢失。下图总结了各种ResNet系列每层的输出大小以及结构中每个点的卷积核的尺寸。四、Pytorch代码实现为了和之前的各个网络比较,我们还是使用Fashion-MNIST数据集,相对而言数据量不大,为了节约时间,下面构建ResNet18为例(一)定义残差块在ResNet-18中,每个残差快都包含2个3×3的卷积层class Residual(nn.Module): def __init__(self, input_channels, num_channels, use_1x1conv=False, strides=1): super().__init__() self.conv1 = nn.Conv2d(input_channels, num_channels, kernel_size=3, padding=1, stride=strides) self.conv2 = nn.Conv2d(num_channels, num_channels, kernel_size=3, padding=1) if use_1x1conv: self.conv3 = nn.Conv2d(input_channels, num_channels, kernel_size=1, stride=strides) else: self.conv3 = None self.bn1 = nn.BatchNorm2d(num_channels) self.bn2 = nn.BatchNorm2d(num_channels) def forward(self, X): Y = F.relu(self.bn1(self.conv1(X))) Y = self.bn2(self.conv2(Y)) if self.conv3: X = self.conv3(X) Y += X return F.relu(Y) (二)定义ResNet-18ResNet的前两层跟之前介绍的GoogLeNet中的一样: 在输出通道数为64、步幅为2的卷积层后,接步幅为2的的最大汇聚层。 不同之处在于ResNet每个卷积层后增加了批量归一化层。b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3), nn.BatchNorm2d(64), nn.ReLU(), nn.MaxPool2d(kernel_size=3, stride=2, padding=1)) GoogLeNet在后面接了4个由Inception块组成的模块。 ResNet则使用4个由残差块组成的模块,每个模块使用若干个同样输出通道数的残差块。 第一个模块的通道数同输入通道数一致。在后面的每个残差块中,如果是第一个卷积层,则引用带有1×1的残差块def resnet_block(input_channels, num_channels, num_residuals, first_block=False): blk = [] for i in range(num_residuals): if i == 0 and not first_block: blk.append(Residual(input_channels, num_channels, use_1x1conv=True, strides=2)) else: blk.append(Residual(num_channels, num_channels)) return blk 在后面增加所有残差块,这里所有的残差块都是2个卷积层b2 = nn.Sequential(*resnet_block(64, 64, 2, first_block=True)) # *解包 b3 = nn.Sequential(*resnet_block(64, 128, 2)) b4 = nn.Sequential(*resnet_block(128, 256, 2)) b5 = nn.Sequential(*resnet_block(256, 512, 2)) #加入全连接层 net = nn.Sequential(b1, b2, b3, b4, b5, nn.AdaptiveAvgPool2d((1,1)), nn.Flatten(), nn.Linear(512, 10)) (三)加载数据集# 加载Fashion-MNIST数据集 transform = transforms.Compose([ transforms.ToTensor(), transforms.Resize((96,96)), transforms.Normalize((0.5,), (0.5,)) ]) trainset = torchvision.datasets.FashionMNIST(root='./data', train=True, download=True, transform=transform) trainloader = torch.utils.data.DataLoader(trainset, batch_size=128, shuffle=True, num_workers=2) testset = torchvision.datasets.FashionMNIST(root='./data', train=False, download=True, transform=transform) testloader = torch.utils.data.DataLoader(testset, batch_size=128, shuffle=False, num_workers=2) (四)初始化模型Xavier初始化:防止梯度爆炸,这是CNN常用的操作,ResNet这种已经算比较深的网络而言,特别有效,之前我们也介绍过他的具体公式。# Xavier初始化:防止梯度爆炸,这是CNN常用的操作 def init_weights(m): if type(m) == nn.Linear or type(m) == nn.Conv2d: #对全连接层和卷积层初始化 nn.init.xavier_uniform_(m.weight) net.apply(init_weights) # 检查是否有可用的GPU device = torch.device('cuda'if torch.cuda.is_available() else 'cpu') model = net.to(device) # 定义损失函数和优化器 criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(model.parameters(), lr=0.05) (五)模型训练和测试# 训练模型 num_epochs = 10 train_losses = [] test_losses = [] train_accs = [] test_accs = [] for epoch in range(num_epochs): train_loss = 0.0 train_total = 0 train_correct = 0 model.train() for images, labels in trainloader: images, labels = images.to(device), labels.to(device) optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels) loss.backward() optimizer.step() train_loss += loss.item() _, predicted = torch.max(outputs.data, 1) train_total += labels.size(0) train_correct += (predicted == labels).sum().item() train_loss /= len(trainloader) train_accuracy = 100*train_correct / train_total train_losses.append(train_loss) train_accs.append(train_accuracy) test_loss = 0.0 test_total = 0 test_correct = 0 model.eval() with torch.no_grad(): for images, labels in testloader: images, labels = images.to(device), labels.to(device) outputs = model(images) loss = criterion(outputs, labels) test_loss += loss.item() _, predicted = torch.max(outputs.data, 1) test_total += labels.size(0) test_correct += (predicted == labels).sum().item() test_loss /= len(testloader) test_accuracy = 100*test_correct / test_total test_losses.append(test_loss) test_accs.append(test_accuracy) print(f"Epoch {epoch+1}/{num_epochs}: Train Loss: {train_loss:.4f}, Train Acc: {train_accuracy:.2f}%, Test Loss: {test_loss:.4f}, Test Acc: {test_accuracy:.2f}%") # 绘制训练误差和测试误差曲线 plt.figure(figsize=(10, 5)) plt.plot(range(1, num_epochs+1), train_losses, label='Train Loss') plt.plot(range(1, num_epochs+1), test_losses, label='Test Loss') plt.xlabel('Epoch') plt.ylabel('Loss') plt.title('Training and Testing Loss') plt.legend() plt.show() # 绘制训练准确率和测试准确率曲线 plt.figure(figsize=(10, 5)) plt.plot(range(1, num_epochs+1), train_accs, label='Train Acc') plt.plot(range(1, num_epochs+1), test_accs, label='Test Acc') plt.xlabel('Epoch') plt.ylabel('Accuracy') plt.title('Training and Testing Accuracy') plt.legend() plt.show() Epoch 1/10: Train Loss: 0.4473, Train Acc: 84.47%, Test Loss: 0.3017, Test Acc: 88.82% Epoch 2/10: Train Loss: 0.2442, Train Acc: 90.99%, Test Loss: 0.3066, Test Acc: 88.10% Epoch 3/10: Train Loss: 0.1862, Train Acc: 93.07%, Test Loss: 0.2971, Test Acc: 88.83% Epoch 4/10: Train Loss: 0.1436, Train Acc: 94.71%, Test Loss: 0.4492, Test Acc: 85.70% Epoch 5/10: Train Loss: 0.1071, Train Acc: 96.12%, Test Loss: 0.2633, Test Acc: 91.12% Epoch 6/10: Train Loss: 0.0806, Train Acc: 97.14%, Test Loss: 0.2641, Test Acc: 91.58% Epoch 7/10: Train Loss: 0.0575, Train Acc: 98.01%, Test Loss: 0.4005, Test Acc: 88.51% Epoch 8/10: Train Loss: 0.0430, Train Acc: 98.51%, Test Loss: 0.3064, Test Acc: 91.63% Epoch 9/10: Train Loss: 0.0278, Train Acc: 99.12%, Test Loss: 0.3101, Test Acc: 92.04% Epoch 10/10: Train Loss: 0.0192, Train Acc: 99.40%, Test Loss: 0.4140, Test Acc: 91.00% 从结果可以看出,存在较严重的过拟合,但即便这样,测试集上的精度还是达到了0.92,比之前介绍的网络架构都更有效。五、总结ResNet架构通过引入残差模块和跳跃连接来解决深层神经网络中的梯度问题,使得可以训练更深的网络。这种架构在计算机视觉任务中取得了很好的性能,并且对于大多数图像分类、目标检测和语义分割等任务都非常有效。其中,在残差网络中添加恒等映射并没有引入任何额外的参数。因此,网络的计算复杂度不会增加。随着深度的增加,ResNet 的准确率增益会更高。这产生的结果比之前的其他网络(例如 VGG 网络)要好得多。
0
0
0
浏览量1576
乌兰推

【深度学习】:《PyTorch入门到项目实战》第九天:Dropout实现

上一章我们介绍了L2正则化和权重衰退,在深度学习中,还有一个很实用的方法——Dropout,能够减少过拟合问题。之前我们介绍了我们的目的是要训练一种泛化的模型,那么就要求模型的鲁棒性较强。一个还不错的尝试是在训练神经网络时,让模型的结果不那么依赖某个神经元,因此在训练神经网络过程中,我们每次迭代将隐藏层的一些神经元随机丢弃掉,这样就不会使得我们的模型太依赖某一个神经元,从而使得我们的模型在未知的数据集上或许会有更好的泛化能力。下面我们具体来看dropout的原理。1. Dropout理论基础1.1 基本原理假设我们要训练的神经网络如下所示:传统的神经网络是全连接的,也就是每一个神经元都会与下一个神经元连接,而dropout会遍历每一层神经网络,设置神经元消除的概率,然后消除一定比例的神经元和它的进出的连线,从而能够得到一个规模更小的神经网络。 假设每一层消除神经元的概率是0.5,在一次训练迭代中,消除的神经元如下所示:以第一层为例,第二个神经元和第四个神经元消除了,那么其节点及进出的连线全部消除,得到消除后的神经网络如下:可以看出dropout得到了一个更简洁的神经网络。对于每一个训练样本,我们都以dropout之后的神经网络进行训练,这样使得我们的训练样本不会依赖于某个特征。1.2 具体实施在具体实施dropout时,我们介绍最常用的反向随机失活。首先我们需要定义一个随机向量,如果小于丢弃率p,则权重设为0,相当于将这个神经元丢弃。然后对中间值向外扩展,除以1-p,以保障期望不变。具体思想如下:假设在第某一层隐藏层我们有50个神经元,丢弃率为0.2,也就是有10个神经元被归0(丢弃)了,那么我们中间值的期望减少了20%,为了不影响中间值的期望,我们除以1-p来保证其期望不变。具体公式如下此时E(a′)=aE(a')=aE(a′)=a。从这里我们也可以发现,dropout是通过设置权重为0来实现消除神经元,并不是直接将神经元个数减少删除。下面我们来看看具体代码实现部分2. 代码实现2.1 dropout层定义"""导入相关库""" import torch from torch import nn from d2l import torch as d2l """定义dropout函数""" def dropout_layer(X, dropout): ''' 实现丢弃 ''' assert 0 <= dropout <= 1#断言,确保dropout在0-1之间 # dropout=1,所有元素都被丢弃 if dropout == 1: return torch.zeros_like(X) # dropou=0,所有元素都被保留 if dropout == 0: return X # 其他情况,dropout在0-1之间 mask = (torch.rand(X.shape) > dropout).float()#返回0和1的向量 return mask * X / (1.0 - dropout)#进行中间值拓展 通过上面定义的dropout_layer函数,我们下面以一个具体的小例子来测试一下X= torch.arange(8, dtype = torch.float32).reshape((2, 4))# 定义一个张量 print(X)#不进行dropout情况 print(dropout_layer(X, 0.))#dropout为0 print(dropout_layer(X, 0.5))#dropout为0.5 print(dropout_layer(X, 1.))#dropout为1 tensor([[0., 1., 2., 3.], [4., 5., 6., 7.]]) tensor([[0., 1., 2., 3.], [4., 5., 6., 7.]]) tensor([[ 0., 2., 4., 0.], [ 8., 10., 0., 0.]]) tensor([[0., 0., 0., 0.], [0., 0., 0., 0.]]) 2.2初始化参数# 使用之前的fasion_mnist数据集图像,设置具有两个隐藏层的神经网络 num_inputs, num_outputs, num_hiddens1, num_hiddens2 = 784, 10, 256, 256 这是一个具有两个隐藏层的神经网络,结构如下(具体神经元个数不同):2.3 模型定义使用dropout定义在每个隐藏层的输出中,其中不同层的p设置不同。一个比较常用的做法是:越接近输入层的,p设置的越小。因为一开始我们不希望输入信息丢失太多,因此该模型的结构是 :linear--Relu--dropout--linear--Relu--dropout--linear下面我们来看看具体代码是如何实现的,假设第一层dropout的概率为0.2,第二层为0.5dropout1, dropout2 = 0.2, 0.5 class Net(nn.Module): def __init__(self, num_inputs, num_outputs, num_hiddens1, num_hiddens2, is_training = True): super(Net, self).__init__() self.num_inputs = num_inputs self.training = is_training self.lin1 = nn.Linear(num_inputs, num_hiddens1)#定义线性层 self.lin2 = nn.Linear(num_hiddens1, num_hiddens2) self.lin3 = nn.Linear(num_hiddens2, num_outputs) self.relu = nn.ReLU()#定义Relu激活函数 def forward(self, X): H1 = self.relu(self.lin1(X.reshape((-1, self.num_inputs)))) # 只有在训练模型时才使用dropout if self.training == True: # 在第一个全连接层之后添加一个dropout层 H1 = dropout_layer(H1, dropout1) H2 = self.relu(self.lin2(H1)) if self.training == True: # 在第二个全连接层之后添加一个dropout层 H2 = dropout_layer(H2, dropout2) out = self.lin3(H2) return out #在这里没有定义softmax回归,因为在定义损失函数时,CrossEntropyLoss会自动计算softmax net = Net(num_inputs, num_outputs, num_hiddens1, num_hiddens2) 2.4 模型训练num_epochs, lr, batch_size = 10, 0.5, 256 loss = nn.CrossEntropyLoss(reduction='none') train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size) trainer = torch.optim.SGD(net.parameters(), lr=lr) d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer) 2.5 简洁代码实现下面我们使用nn内置方法来实现dropout的神经网络.神经网络的结构如下: linear-relu-dropout-linear-dropout-relu-linear""" 构建神经网络 """ net = nn.Sequential(nn.Flatten(), nn.Linear(784, 256), nn.ReLU(), # 在第一个全连接层之后添加一个dropout层 nn.Dropout(dropout1), nn.Linear(256, 256), nn.ReLU(), # 在第二个全连接层之后添加一个dropout层 nn.Dropout(dropout2), nn.Linear(256, 10)) """ 初始化权重 """ def init_weights(m): if type(m) == nn.Linear: """对于线性层,使用正态分布初始化权重""" nn.init.normal_(m.weight, std=0.01) net.apply(init_weights) trainer = torch.optim.SGD(net.parameters(), lr=lr) d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer) 3.总结1.==dropout有效的原理,直观上理解==:不要依赖于任何一个特征,因为该单元的输入可能随时被清除,或者说该单元的输入也都可能被随机清除。因此我们不愿意把所有赌注都放在一个节点上,不愿意给任何一个输入加上太多权重,因为它可能会被删除,因此该单元将通过这种方式积极地传播开,并为单元的每个输入增加一点权重,通过传播所有权重,dropout将产生收缩权重的平方范数的效果。2.==dropout一大缺点就是没有明确的损失函数==,每次迭代,都会随机移除一些节点,如果再三检查梯度下降的性能,实际上是很难进行复查的,因为我们没有定义明确的损失函数。3.==dropout只在训练集上进行,而不在测试集上使用。== 因为在测试阶段进行预测时,我们不期望输出结果是随机的,如果测试阶段应用dropout函数,预测会受到干扰。
0
0
0
浏览量708
乌兰推

【深度学习】:《PyTorch入门到项目实战》(十二)填充(padding)和步幅(stride)

前言有时候,输出远远小于输入,这是因为卷积核的影响,而在原始图像较小的情况下,任意丢失很多信息,这个时候我们需要使用填充是解决此问题。 有时,我们可能希望大幅降低图像的宽度和高度。例如,我们发现一个图像实在是太大了。这个时候使用步幅可以快速将输出变小。1. padding为了构建深度神经网络,你需要学会使用的一个基本的卷积操作就是padding。首先让我们来回忆一下卷积是如何计算的:这其实有两个缺陷:第一个是如果每一次使用一个卷积操作,我们的图像都会缩小。 例如我们从 6x6 通过一个 3x3的卷积核,做不了几次卷积,我们的图片就会变得非常小,也许它会缩小到只有1x1。第二个缺陷是图片角落或者边际上的像素只会在输出中被使用一次 因为它只通过那个3x3的过滤器(filter)一次 然而图片中间的一个像素,会有许多3x3的过滤器(filter)在那个像素上重叠 所以相对而言 角落或者边界上的像素被使用的次数少很多,这样我们就丢失了许多图片上靠近边界的信息。所以为了同时解决上述的两个问题。我们能做的是在使用卷积操作前,对图片进行填充,通常是用0来进行填充,具体如下所示。2.步幅(stride)卷积窗口从输入张量的左上角开始,向下、向右滑动。 在前面的例子中,我们默认每次滑动一个元素。 但是,有时候为了高效计算或是缩减采样次数,卷积窗口可以跳过中间位置,每次滑动多个元素。卷积中的步幅是另一个构建卷积神经网络的基本操作,例如,下面是一个步幅为3的情况。3.代码实现3.1 padding实现在pytorch中,padding和stride的都可以在nn中实现# 导入相关库 import torch from torch import nn # 定义计算卷积层函数 def comp_conv2d(conv2d, X): # 这里的(1,1)表示批量大小和通道数都是1 X = X.reshape((1, 1) + X.shape) Y = conv2d(X) # 省略前两个维度:批量大小和通道 return Y.reshape(Y.shape[2:]) # 请注意,padding参数这里每边都填充了1行或1列,因此总共添加了2行或2列 conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1)#因此这里相当于是一个3×3的kernel加padding=1,那么根据我们的公式可以得到,最终得到的输出和输入一致 X = torch.rand(size=(8, 8)) comp_conv2d(conv2d, X).shape torch.Size([8, 8]) 3.2 stride实现conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1, stride=2) comp_conv2d(conv2d, X).shape torch.Size([4, 4])
0
0
0
浏览量391
乌兰推

【深度学习】:《PyTorch入门到项目实战》第10天:梯度爆炸、梯度消失、梯度检验

写在前面1.梯度消失我们在之前介绍了sigmoid激活函数,但是它现在不常用了,因为他就是导致梯度消失的一个常见的情况。现在我们来看看sigmoid的梯度情况%matplotlib inline import torch import matplotlib.pyplot as plt from torch import nn x = torch.arange(-8.0, 8.0, 0.1, requires_grad=True) y = torch.sigmoid(x) y.backward(torch.ones_like(x)) plt.plot(x.detach().numpy(), y.detach().numpy(),'--',label='sigmoid') plt.plot(x.detach().numpy(), x.grad.numpy(),'r--',label='gradient') plt.legend(loc = 'best') plt.grid(True) 可以看出,当sigmoid函数的输入很大或是很小时,它的梯度都会消失。 此外,当反向传播通过许多层时, 这些地方sigmoid函数的输入接近于零,可能导致整个乘积的梯度可能会消失。因此,ReLU激活函数成为了大家默认的选择2.梯度爆炸同样的,梯度爆炸也是一个非常严重的问题,它会让导致梯度下降很难收敛。下面我们举一个简单的例子,来看看梯度爆炸的情况,我们定义一个4×4的随机矩阵,满足标准正态分布,观察乘以50次之后的结果M = torch.normal(0, 1, size=(4,4)) print('一个矩阵 \n',M) for i in range(50): M = torch.mm(M,torch.normal(0, 1, size=(4, 4))) print('乘以50个矩阵后\n', M) 一个矩阵 tensor([[ 1.2106, -1.2478, 0.9032, 0.1750], [-0.4060, 0.7475, -2.2134, -2.1323], [-1.0121, 0.1883, 1.7743, -0.6649], [ 0.1302, 0.2794, 0.0039, -0.2880]]) 乘以50个矩阵后 tensor([[-6.4536e+12, 2.9500e+11, -3.1643e+12, 3.2686e+12], [ 4.9242e+12, -2.2509e+11, 2.4144e+12, -2.4940e+12], [-1.8533e+12, 8.4715e+10, -9.0872e+11, 9.3866e+11], [-6.2537e+11, 2.8586e+10, -3.0663e+11, 3.1674e+11]]) 3.初始化权重"""定义一个神经网络""" net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 1)) """正态分布初始化""" def norm(m): if type(m) == nn.Linear:#对线性层进行初始化权重 nn.init.normal_(m.weight, mean=0, std=0.01)#正态分布初始化 """Xavier初始化""" def Xavier(m): if type(m) == nn.Linear: nn.init.xavier_uniform_(n.weight)#Xavier初始化 net[0].apply(norm)#对第一个线性层进行正态初始化 print(net[0].weight) net[2].apply(Xavier)#对第二个线性层进行Xavier初始化 print(net[2].weight) Parameter containing: tensor([[ 2.6455e-03, -9.4835e-03, -2.3148e-03, 7.3588e-03], [ 8.4367e-03, -6.8525e-03, 4.5711e-03, 6.6946e-04], [-1.7318e-03, -2.4081e-03, -5.8394e-03, 4.2219e-03], [-8.6585e-03, 1.5090e-02, 1.2062e-02, 4.7167e-03], [ 8.8256e-03, -7.8020e-05, -1.7378e-03, -2.5176e-02], [-1.1565e-02, 1.7698e-03, -1.8693e-02, 8.1501e-05], [-1.3891e-02, -6.1892e-03, -4.7369e-03, 9.8099e-03], [ 3.5225e-04, 4.4494e-03, -2.1365e-03, 4.0189e-03]], requires_grad=True) Parameter containing: tensor([[ 0.1272, 0.1947, -0.1398, -0.0676, 0.1016, -0.2671, -0.1270, -0.3072]], requires_grad=True) 4.梯度的数值计算5.梯度检验
0
0
0
浏览量917
乌兰推

池化层(Pooling layers)

1.理论部分在进行卷积层计算时候,有一个问题就是边缘的地方容易忽略,并且对位置是非常敏感的。池化层的做法是为了降低卷积层对位置的敏感性,同时降低对空间降采样表示的敏感性。 因此,除了卷积层,卷积网络也经常使用池化层来缩减模型的大小,提高计算速度,同时提高所提取特征的鲁棒性。与卷积层不同的是,池化层运算是确定性的,我们通常计算池化窗口中所有元素的最大值或平均值。这些操作分别称为最大池化层(maximum pooling)和平均池化层(average pooling)。先举一个池化层的例子假如输入是一个4×4矩阵,用到的池化类型是最大池化(max pooling)。执行最大池化是一个2×2矩阵。执行过程非常简单,把4×4的输入拆分成不同的区域,我把这个区域用不同颜色来标记。对于2×2的输出,输出的每个元素都是其对应颜色区域中的最大元素值。左上区域的最大值是9,右上区域的最大元素值是2,左下区域的最大值是6,右下区域的最大值是3。为了计算出右侧这4个元素值,我们需要对输入矩阵的2×2区域做最大值运算。这里相当于是一个2×2的kernel,步幅为2。因为我们使用的过滤器为2×2,最后输出是9。然后向右移动2个步幅,计算出最大值2。然后是第二行,向下移动得到最大值6。最后向右移动,得到最大值3。这是一个2×2矩阵,即f=2f=2f=2,步幅是2,即s=2s=2s=2。这是对最大池化功能的直观理解,你可以把这个4×4区域看作是某些特征的集合,也就是神经网络中某一层的非激活值集合。数字大意味着可能探测到了某些特定的特征,左上象限具有的特征可能是一个垂直边缘,最大化操作的功能就是只要在任何一个象限内提取到某个特征,它都会保留在最大化的池化输出里。所以最大化运算的实际作用就是,如果在过滤器中提取到某个特征,那么保留其最大值。如果没有提取到这个特征,可能在右上象限中不存在这个特征,那么其中的最大值也还是很小。其中一个有意思的特点就是,它有一组超参数,但并没有参数需要学习。实际上,梯度下降没有什么可学的,一旦确定了fff和sss,它就是一个固定运算,梯度下降无需改变任何值。目前来说,最大池化比平均池化更常用。但也有例外,就是深度很深的神经网络,你可以用平均池化来分解规模为7×7×1000的网络的表示层,在整个空间内求平均值,得到1×1×1000。但在神经网络中,最大池化要比平均池化用得更多。2.代码实现池化层代码非常简单,因为这里没有卷积核,输出为输入中每个区域的最大值或平均值,我们可以很简单的自定义一个池化层函数import torch from torch import nn def pool2d(X, pool_size, mode='max'): p_h, p_w = pool_size #池化层大小 Y = torch.zeros((X.shape[0] - p_h + 1, X.shape[1] - p_w + 1)) for i in range(Y.shape[0]): for j in range(Y.shape[1]): if mode == 'max': Y[i, j] = X[i: i + p_h, j: j + p_w].max() elif mode == 'avg': Y[i, j] = X[i: i + p_h, j: j + p_w].mean() return Y 现在我们测试一下,先生成一个张量XX = torch.tensor([[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]]) X # 测试我们刚刚的函数,默认最大池化层 pool2d(X, (2, 2)) tensor([[4., 5.], [7., 8.]]) 3.填充与步幅与卷积层一样,池化层也可以改变输出形状。和以前一样,我们可以通过填充(padding)和步幅(stride)以获得所需的输出形状。 这里我们直接使用pytorch内置的方法来演示,首先生成输入张量X,它是一个四维张量,其中样本数和通道数都是1。X = torch.arange(16, dtype=torch.float32).reshape((1, 1, 4, 4)) X tensor([[[[ 0., 1., 2., 3.], [ 4., 5., 6., 7.], [ 8., 9., 10., 11.], [12., 13., 14., 15.]]]]) 默认条件下,步幅和池化层的窗口大小一致。例如,我们使用3×33×33×3的池化层,那么步幅sss默认是3pool2d = nn.MaxPool2d(3) pool2d(X) tensor([[[[10.]]]]) 我们可以根据需要指定相应的步幅和填充,这里我们设置padding=1,stride=2通常情况下,我们不会使用填充(padding),因为这一部分可以在卷积层进行。pool2d = nn.MaxPool2d(3, padding=1, stride=2) pool2d(X) 4.多通道在处理多通道输入数据时,池化层在每个输入通道上单独运算,而不是像卷积层一样在通道上对输入进行汇总。 这意味着汇聚层的输出通道数与输入通道数相同。和我们之前创建多通道数据一样,我们使用cat函数X = torch.cat((X, X + 1), 1) X 此时X的纬度是1×2×4×4,1个样本,两个通道接下来我们使用一个2×2的池化层,步幅sss为2pool2d = nn.MaxPool2d(2, stride=2) pool2d(X) tensor([[[[ 5., 7.], [13., 15.]], [[ 6., 8.], [14., 16.]]]]) 可以看到结果仍然是一个两通道张量,这一点和卷积层完全不同。总结特点:输入通道数等于输出通道数没有可以学习的参数池化层的主要优点之一是减轻卷积层对位置的过度敏感最大池化层:返回窗口内的最大值,提取的是每个窗口中最强的信号,更常用平均池化层:平均汇聚层会输出该窗口内的平均值。
0
0
0
浏览量1868
乌兰推

【深度学习】:《PyTorch入门到项目实战》第八天:权重衰退(含源码)

1.基本概念前一节我们描述了过拟合的问题,虽然我们可以通过增加更多的数据来减少过拟合,但是成本较高,有时候并不能满足。因此现在我们来介绍一些正则化模型的方法。在深度学习中,权重衰退是使用较为广泛的一种正则化方法。具体原理如下。我们引入L2正则化,此时我们的损失函数为:2.代码实现和上一章一样,照样使用模拟数据集,生成数据集如下:2.1 生成数据集这里假设真实的数据如下:下面我们先生成数据集"""导入相关库""" import torch from d2l import torch as d2l from torch import nn %matplotlib inline # 定义相关函数。这是沐神教材中的函数,如果下载了d2l可以直接导入 def synthetic_data(w, b, num_examples): #@save """生成y=Xw+b+噪声""" X = torch.normal(0, 1, (num_examples, len(w))) y = torch.matmul(X, w) + b y += torch.normal(0, 0.01, y.shape) return X, y.reshape((-1, 1)) def load_array(data_arrays, batch_size, is_train=True): """构造一个PyTorch数据迭代器""" dataset = data.TensorDataset(*data_arrays)#将数据转换为tensor return data.DataLoader(dataset, batch_size, shuffle=is_train) """生成数据集""" n_train, n_test, num_inputs, batch_size = 50, 100, 200, 5#定义相关训练集,验证集,输入变量,以及batch的大小 true_w, true_b = torch.ones((num_inputs, 1)) * 0.01, 0.1#定义真实的参数 train_data = d2l.synthetic_data(true_w, true_b, n_train)#生成模拟数据,具体函数如下 train_iter = d2l.load_array(train_data, batch_size)#加载训练集数据 test_data = d2l.synthetic_data(true_w, true_b, n_test) test_iter = d2l.load_array(test_data, batch_size, is_train=False) 根据上一章的介绍,我们知道样本越小越容易造成过拟合,这里我们将样本量设置为100,但是参数却有200个,这种情况下p>n,很容易造成过拟合现象。2.2 初始化参数生成数据集后,下一步就是初始化参数,这里我们对于权重www初始化为标准正态分布,偏差bbb初始化为0def init_params(): w = torch.normal(0, 1, size=(num_inputs, 1), requires_grad=True)#生成标准正态分布 b = torch.zeros(1, requires_grad=True)#生成全部为0的数据 return [w, b] 2.3 定义惩罚项这里我们定义L2正则化,具体代码如下所示def l2_penalty(w): return torch.sum(w.pow(2)) / 2 2.3 训练这里和之前线性回归训练基本一致,唯一不同的是多了一个惩罚项,因此lambd为超参数def train(lambd): w, b = init_params()#初始化参数 net, loss = lambda X: d2l.linreg(X, w, b), d2l.squared_loss#这里使用匿名函数,定义了两个函数,一个是求解模型结果,一个是损失函数 num_epochs, lr = 100, 0.003 """定义相关图形设置""" animator = d2l.Animator(xlabel='epochs', ylabel='loss', yscale='log', xlim=[5, num_epochs], legend=['train', 'test']) """模型训练,更新参数""" for epoch in range(num_epochs): for X, y in train_iter: # 增加了L2范数惩罚项, # 广播机制使l2_penalty(w)成为一个长度为batch_size的向量 l = loss(net(X), y) + lambd * l2_penalty(w) l.sum().backward() d2l.sgd([w, b], lr, batch_size) """绘制训练误差和测试误差""" if (epoch + 1) % 5 == 0: animator.add(epoch + 1, (d2l.evaluate_loss(net, train_iter, loss), d2l.evaluate_loss(net, test_iter, loss))) print('w的L2范数是:', torch.norm(w).item()) 首先,我们来看看不增加惩罚项的情况,即和我们之前的线性回归一致,此时,存在严重的过拟合现象,如下图所示train(lambd=0) 从上图结果来看,存在严重的过拟合问题,验证误差远远比训练误差大。下面我们来看看lambd为5的情况下的结果train(lambd=5) 可以看出,随着lambd的增加,验证误差不断减少,但是还是存在过拟合。def train_concise(wd): net = nn.Sequential(nn.Linear(num_inputs, 1))#定义线性神经网络 for param in net.parameters(): param.data.normal_()#初始化参数 loss = nn.MSELoss(reduction='none')#定义MSE损失函数 num_epochs, lr = 100, 0.003#定义训练次数和学习率 # 偏置参数没有衰减 trainer = torch.optim.SGD([ {"params":net[0].weight,'weight_decay': wd}, {"params":net[0].bias}], lr=lr)#定义权重衰退,其中超参数为wd animator = d2l.Animator(xlabel='epochs', ylabel='loss', yscale='log', xlim=[5, num_epochs], legend=['train', 'test'])#绘图 """训练模型""" for epoch in range(num_epochs): for X, y in train_iter: trainer.zero_grad() l = loss(net(X), y) l.mean().backward() trainer.step() if (epoch + 1) % 5 == 0: animator.add(epoch + 1, (d2l.evaluate_loss(net, train_iter, loss), d2l.evaluate_loss(net, test_iter, loss))) print('w的L2范数:', net[0].weight.norm().item()) train_concise(0) train_concise(3) 3.拓展部分沐神的参考教材中使用的是L2正则化,我们接下来看看使用L1正则化的效果,首先需要定义一下L1正则化,如下所示:def l1_penalty(w): return torch.sum(torch.abs(w)) def train_l1(lambd): w, b = init_params()#初始化参数 net, loss = lambda X: d2l.linreg(X, w, b), d2l.squared_loss#这里使用匿名函数,定义了两个函数,一个是求解模型结果,一个是损失函数 num_epochs, lr = 100, 0.003 """定义相关图形设置""" animator = d2l.Animator(xlabel='epochs', ylabel='loss', yscale='log', xlim=[5, num_epochs], legend=['train', 'test']) """模型训练,更新参数""" for epoch in range(num_epochs): for X, y in train_iter: # 增加了L1范数惩罚项, # 广播机制使l1_penalty(w)成为一个长度为batch_size的向量 l = loss(net(X), y) + lambd * l1_penalty(w) l.sum().backward() d2l.sgd([w, b], lr, batch_size) """绘制训练误差和测试误差""" if (epoch + 1) % 5 == 0: animator.add(epoch + 1, (d2l.evaluate_loss(net, train_iter, loss), d2l.evaluate_loss(net, test_iter, loss))) print('w的L2范数是:', torch.norm(w).item()) train_l1(1) 可以看出使用L1正则化,当lambd为1的时候,就可以使得验证误差基本等于训练误差。其实正如我们之前说的,L2正则化只能将参数压缩,但是不能去除为0,我们这个模拟数据集中,p为200,n为100,p>>n,此时使用L1正则化可以使得某些特征的系数为0,从而更好的缓解过拟合问题。 本章的介绍到此介绍,如果文章对你有帮助,请多多点赞、收藏、评论、关注支持!!
0
0
0
浏览量1411
乌兰推

【深度学习】:PyTorch:数据操作和自动求导

1.数据操作# 导入torch import torch import numpy as np1.1 张量创建x = torch.arange(12) y = np.arange(12)x,y (tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]), array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])) tensor(张量)表示一个数值组成的数组,可以有多个维度,类似numpy中的n维数组,因此很多n维数组有的方法张量也有,下面我们来测试一下有哪些numpy中的方法可以在这里使用。==要了解numpy可以看这篇文章==:Python数据分析大杀器之Numpy详解# 查看形状 x.shape torch.Size([12]) # 查看数量长度 len(x) 12 同样可以使用reshape函数转换数组x = x.reshape(3,4) x tensor([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]]) zeros创建全为0的元素x = torch.zeros(3,4) x tensor([[0., 0., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.]]) ones创建全为1的元素x = torch.ones(3,4) x tensor([[1., 1., 1., 1.], [1., 1., 1., 1.], [1., 1., 1., 1.]]) eye创建对角矩阵l = torch.eye(5) l tensor([[1., 0., 0., 0., 0.], [0., 1., 0., 0., 0.], [0., 0., 1., 0., 0.], [0., 0., 0., 1., 0.], [0., 0., 0., 0., 1.]]) ones_like创建形状一致的全为1的元素矩阵x = torch.ones_like(l) x tensor([[1., 1., 1., 1., 1.], [1., 1., 1., 1., 1.], [1., 1., 1., 1., 1.], [1., 1., 1., 1., 1.], [1., 1., 1., 1., 1.]]) randn创建随机矩阵x = torch.randn((2,4)) x tensor([[-0.2102, -1.5580, -1.0650, -0.2689], [-0.5349, 0.6057, 0.7164, 0.4334]]) 可以有多个维度,如下所示,创建两个维度的tensor,其中0表示外面的一层,1表示内部的一层x = torch.tensor([[1,1,1,1],[1,2,3,4],[4,3,2,1]]) x tensor([[1, 1, 1, 1], [1, 2, 3, 4], [4, 3, 2, 1]]) 张量还可以和numpy的数组之间相互转换,具体如下所示y = x.numpy() type(x),type(y) (torch.Tensor, numpy.ndarray) 1.2 基本运算在创建完张量之后,我们对如何计算这些张量感兴趣。和多维数组一样,张量也可以进行一些基本运算,具体代码如下所示x = torch.tensor([1,2,3,4]) y = torch.tensor([2,3,4,5]) x+y,x-y,x*y,x/y (tensor([3, 5, 7, 9]), tensor([-1, -1, -1, -1]), tensor([ 2, 6, 12, 20]), tensor([0.5000, 0.6667, 0.7500, 0.8000])) 可以看出和numpy数组一样,也是对元素进行运算。下面看看求和操作x = torch.arange(12).reshape(3,4) x.sum(dim=0)#按行求和 tensor([12, 15, 18, 21]) y = np.arange(12).reshape((3,4)) y.sum(axis=0)#按行求和 array([12, 15, 18, 21]) 从上面可以看出,tensor和array都可以按行或案列进行操作,但是在torch中,指定dim参数,numpy中,指定axis参数1.3 广播机制我们之前的numpy中介绍过广播机制,在两个数组纬度不同时,可以适当的复制元素来拓展一个纬度或者两个纬度的元素,我们接下来看看torch中是不是也支持广播机制x = torch.tensor([[1,2,3],[4,5,6]]) y = torch.tensor([1,1,1]) z = x + y print('x:',x) print('y:',y) print('z:',z) x: tensor([[1, 2, 3], [4, 5, 6]]) y: tensor([1, 1, 1]) z: tensor([[2, 3, 4], [5, 6, 7]]) 通过上述代码可以发现,torch中也支持广播机制,并且和numpy中的使用基本一致1.4 索引和切片接下来我们来看看如何对tensor结果进行切片和索引,用法和numpy基本一致x tensor([[1, 2, 3], [4, 5, 6]]) # 选取第一列和第二列的数据 x[:,[0,1]] tensor([[1, 2], [4, 5]]) 2.自动微分(求导)线性代数部分大家可以看我的numpy文章,有具体的介绍,这里着重看一下如何求导数。在深度学习中,对于很多层的神经网络而言,人工求导是一件很复杂的事情,因此在如何自动求导是一件很work的事情这里我们假设要对y=xTxy=x^Txy=xTx进行求导。首先我们先初始化一个x值x = torch.arange(4.0) x tensor([0., 1., 2., 3.]) 下面我们在计算梯度之前,需要一个地方来存储梯度,就像我们在进行一些循环时,需要一个空列表来存储内容。下面我们来看如何使用requires_grad_来存储x.requires_grad_(True) print(x.grad)#默认是None,相当于这个时候是一个空列表 None 下面我们来计算yy = torch.dot(x,x) y tensor(14., grad_fn=<DotBackward0>) # 通过反向传播函数计算梯度 y.backward(retain_graph=False) x.grad tensor([0., 2., 4., 6.]) 这里默认情况下,pytorch会保存梯度,因此当我们需要重新计算梯度时,首先要进行初始化,使用grad.zero_x.grad.zero_() # 重新计算y=x的梯度 y = x.sum() y.backward() x.grad tensor([1., 1., 1., 1.]) 上面我们都是先将y变为一个标量再求梯度,如果y不是标量呢?可以先将y求和转换为标量x.grad.zero_() y = x*x y.sum().backward() x.grad tensor([0., 2., 4., 6.]) 2.3 分离微分计算这里沐神给出了一个这样的场景,y是关于x的函数,而z是关于y和x的函数,在我们对z求x偏导时,我们希望将y看做一个常数。这种方法在有的复杂的神经网络模型会很有效,具体通过detach()实现,将u为y的常量具体代码如下:x.grad.zero_()#初始化梯度 y = x * x#y对x的函数 u = y.detach()#将y分离处理 z = u * x#z对x的函数 z.sum().backward()#通过反向传播函数求梯度 x.grad tensor([0., 1., 4., 9.]) 上述结果是什么呢?我们根据求导法则:下面我们来看看u是多少u tensor([0., 1., 4., 9.]) 2.4 控制流梯度计算使用自动微分有一个好处是,当我们的函数是分段的,其也会自动计算得到相应的梯度。下面我们来看一个线性控制流梯度计算案例:def f(a): if a.sum() > 0: b = a else: b = 100 * a return b 首先我们定义一个线性分段函数,如上所示:下面我们来看如何进行自动求导a = torch.randn(12, requires_grad=True) d = f(a) d.backward(torch.ones_like(a)) a.grad == d / a tensor([True, True, True, True, True, True, True, True, True, True, True, True]) 练习与总结1.重新设计一个求控制流梯度的例子,运行并分析结果。在上面的案例中,沐神给了一个线性分段函数的例子,假设不是线性的呢,下面我们假设一个分段函数是这样的具体控制流代码如下:def f(x): if x.norm() > 10: y = x else: y = x*x return y x = torch.randn(12,requires_grad=True) y = f(x) y.backward(torch.ones_like(x)) x.grad tensor([ 0.3074, -2.0289, 0.5950, 1.2339, -2.2543, 0.5834, -2.3040, -1.9097, 0.9255, 1.6837, -1.4464, -0.3131]) 2.绘制微分图import numpy as np import matplotlib.pyplot as plt %matplotlib inline x = torch.linspace(-2*torch.pi, 2*torch.pi, 100) x.requires_grad_(True) y = torch.sin(x) y.sum().backward() y = torch.detach(y) plt.plot(y,'r--',label='$sin(x)$') plt.plot(x.grad,'g',label='$cos(x)$') plt.legend(loc='best') plt.grid() ​
0
0
0
浏览量2020
乌兰推

【深度学习】:《PyTorch入门到项目实战》第五天:从0到1实现Softmax回归

softmax回归模型是logistic回归模型在多分类问题上的推广,在多分类问题中,类标签y可以取两个以上的值。本文基于MNIST手写数字数据集来演示如何使用Pytorch实现softmax回归。🎄🍓1. 数据集导入首先我们来简单的介绍一些softmax回归基本模型,基本思路如下:损失函数使用交叉熵:# 当如相关库 import torch import torch.nn as nn from torchvision import datasets,transforms from torch.utils import data import matplotlib.pyplot as plt import numpy as np import torch.optim as optim 在这里与之前不同的是我们导入了torchvision,它是处理计算机视觉常用的一个库。沐神在这里使用了FashionMnist数据集,我在这里还是使用Mnist数据集,具体的下载代码如下所示。其中train参数可以设置训练集和测试集trans = transforms.ToTensor() train = datasets.MNIST(root='./data',download=True,train=True,transform=trans) test = datasets.MNIST(root='./data',download=True,train=False,transform=trans) Mnist数据集由10个数字的图像组成的。其中训练集有60000张图片,测试集有10000张图片。训练集用于模型的拟合,测试集用于评估模型的好坏len(train), len(test) (60000, 10000) 每张图片的像素均是28*28,并且是灰度图像,所以通道数为1train[0][0].shape torch.Size([1, 28, 28]) 我们来看一下训练集中的特征和标签,.X, y = next(iter(data.DataLoader(train, batch_size=25))) y tensor([5, 0, 4, 1, 9, 2, 1, 3, 1, 4, 3, 5, 3, 6, 1, 7, 2, 8, 6, 9, 4, 0, 9, 1, 1]) y代表的是0-9的数字,下面我们将图形绘制出来def show_images(imgs, num_rows, num_cols, titles=None, scale=1.5): """绘制图像列表""" figsize = (num_cols * scale, num_rows * scale) _, axes = plt.subplots(num_rows, num_cols, figsize=figsize) axes = axes.flatten() for i, (ax, img) in enumerate(zip(axes, imgs)): if torch.is_tensor(img): # 图片张量 ax.imshow(img.numpy()) else: # PIL图片 ax.imshow(img) ax.axes.get_xaxis().set_visible(False) ax.axes.get_yaxis().set_visible(False) if titles: ax.set_title(titles[i]) return axes X, y = next(iter(data.DataLoader(train, batch_size=25))) show_images(X.reshape(25, 28, 28), 2, 9) ​​可以看到第一张图片是5,第二张图片是0。接下来我们想要做的事情是,给电脑一张图片,如何让其返回一个正确的数字。🍅2.初始化参数因为softmax回归需要输入的数据是一个向量,因此首先我们需要将数据进行转换,下面要注意初始化参数的大小。num_inputs = 784 num_outputs = 10 # 初始化为正态分布 W = torch.normal(0,0.01,size = (num_inputs,num_outputs),requires_grad = True) b = torch.zeros(num_outputs,requires_grad=True) 🍒3.定义softmax回归根据softmax回归定义,我们可以通过以下三步实现:1.对每一项求指数2.求和3.用每一行的数除以和具体实现代码如下def softmax(X): X_exp = torch.exp(X) s = X_exp.sum(1, keepdims=True) return X_exp / s 下面我们举一个简单的例子看一下softmax函数是如何工作的z = torch.rand(3, 5) h = softmax(z) print(h) tensor([[0.1768, 0.1426, 0.2773, 0.2582, 0.1450], [0.1580, 0.1307, 0.2118, 0.2411, 0.2583], [0.1863, 0.2572, 0.1148, 0.1996, 0.2420]]) 这样就得出了每一个样本中每一类的概率进一步定义softmax回归模型def nex(X): return softmax((X.reshape((-1,W.shape[0])).matmul(W)+b)) 🍑4. 损失函数定义在这里我们依然使用交叉熵函数处理多分类问题损失函数:l(y,y^)=−1m∑yilogy^il(y,\hat y) = -\frac{1}{m}\sum y_ilog{\hat y_i}l(y,y^​)=−m1​∑yi​logy^​i​其中yi=0,1y_i=0,1yi​=0,1,y^i\hat{y}_iy^​i​是预测的概率在这里我想介绍两种方法计算损失函数,一种的沐神介绍的,通过索引来进行计算,具体如下所示def cross_entropy(y_hat, y): return - torch.log(y_hat[range(len(y_hat)), y])# 这里使用y来进行索引 这里我们使用了y来进行索引,我们来看看一个具体的例子y_true = torch.tensor([0,1]) y_hat = torch.tensor([[0.1,0.2,0.7],[0.3,0.5,0.2]]) y_hat[[0,1],y_true] tensor([0.1000, 0.5000]) 这里返回的是第一个样本中第一类是正确分类的,和第二个样本中的第二类是正确分类的。所以交叉熵的计算就是−12(1×log(0.1)+1×log(0.5))-\frac{1}{2}(1\times log(0.1)+ 1\times log(0.5))−21​(1×log(0.1)+1×log(0.5))cross_entropy(y_hat,y_true).mean() tensor(1.4979) 等价于:(-np.log(0.1)-np.log(0.5))/2 1.4978661367769954 上面这种方式虽然简洁,但是可能不太好理解,下面介绍一种更直观的方式。首先我们要将y转换成one-hot编码。y_true = torch.tensor([0,1]) y_hat = torch.tensor([[0.1,0.2,0.7],[0.3,0.5,0.2]]) y_one_hot = torch.zeros_like(y_hat) y_one_hot.scatter_(1, y_true.unsqueeze(1), 1) y_one_hot tensor([[1., 0., 0.], [0., 1., 0.]]) 可以看出此时的y_one_hot和y_hat维度相同,并且y_one_hot对应类上的元素是1,其余元素为0,此时再根据公式计算交叉熵即可−12(1×log(0.1)+0×log(0.2)+0×log(0.7)+0×log(0.2)+1×log(0.5)+0×log(0.3)-\frac{1}{2}(1\times log(0.1)+0\times log(0.2)+0\times log(0.7) +0 \times log(0.2) +1\times log(0.5)+0\times log(0.3)−21​(1×log(0.1)+0×log(0.2)+0×log(0.7)+0×log(0.2)+1×log(0.5)+0×log(0.3)cost = (y_one_hot * -torch.log(y_hat)).sum(dim=1).mean() cost tensor(1.4979) 可以看出两种方法得到的结果一致def opt(W,b): return optim.SGD([W,b],lr=0.1) 🍐5.训练模型''' 初始化参数 ''' W = torch.zeros((784, 10), requires_grad=True) b = torch.zeros(10, requires_grad=True) ''' 定义SGD优化器 ''' optimizer = optim.SGD([W, b], lr=0.1) ''' 训练模型 ''' nb_epochs = 1000 for epoch in range(nb_epochs + 1): z = net(X)#计算softmax回归结果 cost = cross_entropy(z,y)#计算损失函数 # SGD求解参数 optimizer.zero_grad()#初始化参数 cost.mean().backward()#后向传播求参数 optimizer.step()#更新参数 if epoch % 100 == 0 : print('Epoch {:4d}/{} Cost: {:.6f}'.format( epoch, nb_epochs, cost.mean().item() )) Epoch 0/1000 Cost: 2.302585 Epoch 100/1000 Cost: 0.055274 Epoch 200/1000 Cost: 0.026265 Epoch 300/1000 Cost: 0.017182 Epoch 400/1000 Cost: 0.012762 Epoch 500/1000 Cost: 0.010150 Epoch 600/1000 Cost: 0.008425 Epoch 700/1000 Cost: 0.007202 Epoch 800/1000 Cost: 0.006290 Epoch 900/1000 Cost: 0.005582 Epoch 1000/1000 Cost: 0.005018 🍏6.模型预测首先我们从测试集中随机抽取10个样本X_test, y_test = next(iter(data.DataLoader(test, batch_size=10))) show_images(X_test.reshape(10, 28, 28), 2, 5) array([<AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>], dtype=object) ​测试集拿到的十个数字为7,2,1,0,4,1,4,9,5,9下面我们用刚刚训练好的模型来预测,看看结果如何z = net(X_test) predict = z.argmax(dim=1) predict tensor([7, 3, 1, 0, 4, 1, 4, 1, 4, 7]) 可以看出预测的结果有六个正确,四个错误,模型效果一般。因为我们刚刚只使用了训练集中的25个样本,所以在训练集上预测效果并不好。如何提升预测精度问题将在后续讨论。🍎7.使用内置api简单实现softmax回归上面我们演示了如何从0到1实现softmax回归,在pytorch中,有内置的api可以直接帮我们更简洁的实现,具体代码如下from torch import nn # 一样导入数据集 X, y = next(iter(data.DataLoader(train, batch_size=25))) # 定义模型 net = nn.Sequential(nn.Flatten(), nn.Linear(784, 10))#nn.Flatten()的作用是将输入的特征转换为一个向量 def init_weights(m): if type(m) == nn.Linear: nn.init.normal_(m.weight, std=0.01)#初始化参数 net.apply(init_weights) #计算损失函数 loss = nn.CrossEntropyLoss(reduction='none') # 定义SGD优化器 trainer = torch.optim.SGD(net.parameters(), lr=0.1) nb_epochs = 1000 for epoch in range(nb_epochs + 1): z = net(X)#计算模型结果 cost = loss(z,y)#计算损失函数 # SGD求解参数 trainer.zero_grad()#初始化参数 cost.mean().backward()#后向传播求参数 trainer.step()#更新参数 if epoch % 100 == 0 : print('Epoch {:4d}/{} Cost: {:.6f}'.format( epoch, nb_epochs, cost.mean().item() )) Epoch 0/1000 Cost: 2.318002 Epoch 100/1000 Cost: 0.062154 Epoch 200/1000 Cost: 0.028716 Epoch 300/1000 Cost: 0.018596 Epoch 400/1000 Cost: 0.013739 Epoch 500/1000 Cost: 0.010891 Epoch 600/1000 Cost: 0.009021 Epoch 700/1000 Cost: 0.007699 Epoch 800/1000 Cost: 0.006715 Epoch 900/1000 Cost: 0.005955 Epoch 1000/1000 Cost: 0.005349
0
0
0
浏览量235
乌兰推

【深度学习】:《PyTorch入门到项目实战》(十一):卷积层

卷积神经网络(CNN):卷积层实现之前已经介绍了基本的神经网络知识以及一些处理过拟合欠拟合的概念。现在我们正式进入卷积神经网络的学习。CNN是⼀类强⼤的、为处理图像数据⽽设计的神经⽹络。基于卷积神经⽹络架构的模型在计算机视觉领域中已经占主导地位,当今⼏乎所有的图像识别、⽬标检测或语义分割相关的学术竞赛和商业应⽤都以这种⽅法为基础。对于计算机视觉而言,面临的一个重大挑战就是数据的输入可能会很大。例如,我们有一张64×\times× 64的图片,假设通道数为3,那么它是数据量相当于是一个64×64×3=1228864\times 64\times 3=1228864×64×3=12288的特征向量。当我们要操作更大的图片时候,需要进行卷积计算,它是卷积神经网络中非常重要的一部分。1.引入让我们举个例子,假设给了我们这样一张图片让电脑去搞清楚这张照片里有什么物体,我们可能做的第一件事是检测图片中的垂直边缘。比如说,在这张图片中的栏杆就对应垂直线,与此同时,这些行人的轮廓线某种程度上也是垂线,这些线是垂直边缘检测器的输出。同样,我们可能也想检测水平边缘,比如说这些栏杆就是很明显的水平线,所以如何在图像中检测这些边缘?我们可以构建一个3×33\times33×3的矩阵,我们也称为过滤器或者核函数(kernel)。下面我们通过Andrew Ng的解释来看看为什么这个能做边缘检测?上图一个简单的6×6图像,左边的一半是10,右边一般是0。如果你把它当成一个图片,左边那部分看起来是白色的,像素值10是比较亮的像素值,右边像素值比较暗,我使用灰色来表示0,尽管它也可以被画成黑的。图片里,有一个特别明显的垂直边缘在图像中间,这条垂直线是从黑到白的过渡线,或者从白色到深色。所以,当我们用一个3×3过滤器进行卷积运算的时候,这个3×3的过滤器结果,在左边有明亮的像素,然后有一个过渡,0在中间,然后右边是深色的。卷积运算后,我们得到的是右边的矩阵。还有许多其他的边缘检测方法,关于具体的计算机视觉任务,我们在后续介绍,接下来我们来看看卷积具体是怎么计算的2.卷积运算下面我们来看看卷积具体是怎么计算的,给一个输入矩阵和一个核函数,我们将从输入特征的左上角开始与核函数求内积,然后在进行滑动窗口,求下一个内积。得到我们的输出,具体计算如下可以看出,通过卷积计算后,我们的原始数据特征变小了。假设输入矩阵为n×nn\times nn×n,核函数(Kernel)为f×ff\times ff×f,通常核是一个方阵形式。那么得到的输出结果为(n−f+1)×(n−f+1)(n-f+1)\times (n-f+1)(n−f+1)×(n−f+1)3 代码实现3.1下面我们来简单的实现卷积运算"""导入相关库""" import torch from torch import nn def corr2d(X,K): """定义卷积运算""" h, w = K.shape#核的shape,通常这里的h和w是相等的 Y = torch.zeros((X.shape[0]-h+1, X.shape[1]-w+1))#初始化输出结果 for i in range(Y.shape[0]):#输出矩阵的第i行 for j in range(Y.shape[1]):#输出矩阵的第j列 Y[i,j] = (X[i:i+h,j:j+w] * K).sum()#计算对应内积 return Y 下面我们来测试一下是否正确X = torch.Tensor([[0.0,1.0,2.0],[3.0,4.0,5.0],[6.0,7.0,8.0]]) K = torch.Tensor([[0.0,1.0],[2.0,3.0]]) corr2d(X,K) 可以看出和我们之前的结果一致.3.2 构造卷积层之前我们介绍了如何构造线性层,激活函数,以及drop-out,类似的,我们通过定义一个类来定义卷积层class Conv2D(nn.Module): """定义二维卷积层""" def __init__(self, kernel_size):#定义核函数的大小,这是一个超参数 super().__init__() self.weight = nn.Parameter(torch.rand(Kernel_size))#学习的参数 self.bias = nn.Parameter(torch.zeros(1))#学习的参数 def forward(self, x): return corr2d(x,self.weight)+self.bias#计算卷积结果 3.3 检测图像颜色边缘首先我们定义一个X,假设1代表灰色,0代表白色X = torch.ones(6,6) X[:,2:4] = (0.0) X 下面我们进行边缘检测,在这里我们构造一个1×21\times21×2的核函数K = torch.tensor([[1.0,-1.0]]) K Y = corr2d(X,K) Y 其中,1表示从灰色到白色的垂直边缘,-1表示从白色到黑色的垂直边缘上述定义的核只能检测垂直边缘,现在假设我们对X进行转置,我们想要检测水平边缘Y = corr2d(X.t(),K) Y 可以看见此时的核函数(Kernel)不能够检测水平边缘,我们需要对kernel也进行转置Y = corr2d(X.t(),K.t()) Y 3.4 学习卷积核刚刚是我们自己定义的卷积核,但是当如果我们需要进行更复杂的计算时,直接定义卷积核是很困难的,我们下面来看看能不能通过输入和输出矩阵来学习到我们的卷积核,这里我们定义损失函数为Y和卷积层输出的平方误差。# 构造⼀个⼆维卷积层,它具有1个输出通道和形状为(2,2)的卷积核 conv2d = nn.Conv2d(1,1, kernel_size=(1, 2), bias=False) # 这个⼆维卷积层使⽤四维输⼊和输出格式(批量⼤⼩、通道、⾼度、宽度), # 其中批量⼤⼩和通道数都为1 X = X.reshape((1, 1, 6, 6)) Y = Y.reshape((1, 1, 6, 5)) lr = 3e-2 # 学习率 for i in range(10): Y_hat = conv2d(X)#计算卷积 l = (Y_hat - Y) ** 2#损失函数 conv2d.zero_grad() l.sum().backward()#反向传播计算梯度 # 迭代卷积核 conv2d.weight.data[:] -= lr * conv2d.weight.grad if (i + 1) % 2 == 0: print(f'epoch {i+1}, loss {l.sum():.3f}') 可以看出经过10次迭代后,误差已经较低了,我们下面看看学习到的卷积核参数conv2d.weight
0
0
0
浏览量1728
乌兰推

【深度学习】:《PyTorch入门到项目实战》第六天:多层感知机(含代码)

上一章中介绍了如何使用softmax回归来进行多分类问题,对于一些基本线性模型基本介绍完毕,下面正式进入深度神经网络的学习。先介绍一个比较基础的模型,多层感知机,它是神经网络的最基础模型。首先我们来看看感知机✍🍓1.感知机感知机是一个非常简单的处理二分类的模型,首先我们来看看一个简单的例子对于给定输入xxx,权重www,和偏移bbb,感知机输出结果为:从这个可以看出如果yi(wxi+b)>0y_i(wx_i+b)>0yi​(wxi​+b)>0,则分类正确,否则分类错误。因此感知机训练过程为:其中等价于一个梯度下降,其中损失函数为:因此感知机的基本思想是找到一个超平面来将两个类别分隔开来,如下图所示# 导入相关库 import torch from torch import nn import matplotlib.pyplot as plt import seaborn as sns from torchvision import datasets,transforms from torch.utils import data %matplotlib inline # 创建x,y x = torch.tensor([[0.5,0.25],[0.2,0.5],[0.3,1.1],[0.4,1.1]]) y = torch.tensor([1,1,0,0]) sns.set(style="white") sns.relplot(x[:,0],x[:,1],hue=y) plt.plot([0,1],[1,0],'r') ​​上面给出的平面将0,1两类完全分隔开来。但是感知机有一个问题是,解决不了异或问题(XOR),下面我们来介绍一下异或问题🍅2.异或问题异或问题,当两个相同时,返回正类,不同时,返回负类,具体如下所示# 创建x,y x = torch.tensor([[1,1],[-1,1],[-1,-1],[1,-1]]) y = torch.tensor([1,0,1,0]) sns.set(style="white") sns.relplot(x[:,0],x[:,1],hue=y) ​可以看出,在这种情况下,无论如何我们没办法找到一个超平面将其分开,这是一组线性不可分的数据,在这种情况下,==感知机解决不了异或问题==,因此,神经网络的研究暂缓发展。多年之后,提出了多层感知机,自此开起了神经网络的新发展🍒3多层感知机多层感知机通过在网络中==加入一个或多个隐藏层==来克服线性模型的限制,是一个简单的神经网络,也是深度学习的重要基础,具体如下图所示这是一个具有一个隐藏层的多层感知机,其中输入有4个特征,输出有三个特征,隐藏层有五个隐藏单元,每一层的权重和偏差维度如下:其中,nln_lnl​表示第n层神经元的数量。具体计算过程如下:其中σ()\sigma()σ()表示激活函数==每一层的输出是下一个层的输入,直到生成最后的输出==🍑4. 激活函数如果只是做线性计算的话,那可以转换成一个没有隐藏层的神经网络,因此需要引入激活函数,注意:激活函数必须是非线性的。将激活函数的输出来作为下一层的输入。常用的激活函数可以分为以下几类:🍐4.1 sigmoid函数在logistic回归中,我们就运用了sigmoid函数,它能够将输入转换到0-1之间,呈现出S曲线。在处理二分类问题时,==可以考虑使用sigmoid函数用于输出层。== 具体图形如下# sigmoid 函数 x = torch.arange(-8.0, 8.0, 0.01, requires_grad=True) y = torch.sigmoid(x) plt.ylabel("y = sigmoid(X)")#y标签 plt.grid(True)#显示网格 plt.plot(x.detach(), y.detach()) ​​可以看出,当接近0时,变化最大,说明此时导数值最大,对sigmoid函数求导,可得它的导数图如下:y.backward(torch.ones_like(x),retain_graph=True) plt.plot(x.detach(), x.grad) ​​🍏4.2 Relu激活函数最受欢迎的激活函数是修正线性单元(Rectified linear unit,ReLU), 因为它实现简单,同时在各种预测任务中表现良好。 ReLU提供了一种非常简单的非线性变换。 给定元素(x),ReLU函数被定义为该元素与(0)的最大值。==因此往往在隐藏层中,我们使用Relu激活函数==。其图形如下x = torch.arange(-8.0, 8.0, 0.1, requires_grad=True) y = torch.relu(x) plt.ylabel("y = Relu(X)")#y标签 plt.grid(True)#显示网格 plt.plot(x.detach(), y.detach()) ​​可以看出,当输入值为负数,其导数为0,输入值为正时,其导数为1,虽然在0处不可导,但是这种情况一般不会出现,因为输入往往不会为0,下面我们绘制Relu函数的导数y.backward(torch.ones_like(x),retain_graph=True) plt.plot(x.detach(), x.grad) ​​使用ReLU的原因是,它求导表现得特别好:要么让参数消失,要么让参数通过。 这使得优化表现得更好,==并且ReLU减轻了困扰以往神经网络的梯度消失问题==,当然还有一些Relu函数的变形🍎4.3 tanh函数tanh函数与sigmoid函数类似,将输入压缩到(-1,1)之间。其图形如下# tanh(x) y = torch.tanh(x) plt.ylabel('y=tanh(x)') plt.grid(True) plt.plot(x.detach(), y.detach()) ​​可以看出图形结果和sigmoid函数形状基本一致,唯一有区别的是tanh是关于原点对称的,下面对其进行求导,得到以下结果# 清除以前的梯度 y.backward(torch.ones_like(x),retain_graph=True) plt.plot(x.detach(), x.grad) plt.grid(True) ​​求导的结果也和sigmoid函数结果类似。🥭5.模型训练数据集导入 trans = transforms.ToTensor() train = datasets.MNIST(root='./data',download=True,train=True,transform=trans) test = datasets.MNIST(root='./data',download=True,train=False,transform=trans) # 分批次加载数据集 batch_size = 64 df_train = data.DataLoader(train, batch_size, shuffle=True, ) df_test = data.DataLoader(test, batch_size, shuffle=True, ) X, y = next(iter(df_train)) 参数初始化 num_in,num_out, num_hid = 784,10,64 W1 = nn.Parameter( torch.randn(num_in,num_hid,requires_grad=True)) b1 = nn.Parameter( torch.zeros(num_hid,requires_grad = True)) W2 = nn.Parameter(torch.randn(num_hid,num_out,requires_grad=True)) b2 = nn.Parameter( torch.zeros(num_out)) params = [W1,b1,W2,b2] 定义使用的激活函数 def relu(X): a = torch.zeros_like(X) return torch.max(X, a) 计算模型 def net(X): X = X.reshape((-1, num_in)) H = relu(X@W1 + b1) # 这里“@”代表矩阵乘法 return (H@W2 + b2) 损失函数 loss = nn.CrossEntropyLoss(reduction='none') # SGD优化器 optimizer = torch.optim.SGD(params=params, lr=1) 模型训练for epoch in range(1000): optimizer.zero_grad() hypothesis = net(X) cost = loss(hypothesis, y) cost.mean().backward() optimizer.step() if epoch % 100 == 0: print(epoch, cost.mean().item()) 0 79.99055480957031 100 0.4515937864780426 200 0.30940207839012146 300 0.25154954195022583 400 0.2046610414981842 500 0.18980586528778076 600 0.1860966980457306 700 0.1445973813533783 800 0.13446013629436493 900 0.09926929324865341 🍍6.简洁代码实现''' 定义模型 ''' net = nn.Sequential(nn.Flatten(), nn.Linear(784, 64), nn.ReLU(), nn.Linear(64, 10)) ''' 初始化权重 ''' def init_weights(m): if type(m) == nn.Linear: nn.init.normal_(m.weight, std=0.01) net.apply(init_weights) ''' 损失函数 ''' loss = nn.CrossEntropyLoss() ''' SGD优化器 ''' trainer = torch.optim.SGD(net.parameters(), lr=0.1) ''' 模型训练 ''' for epoch in range(1000): trainer.zero_grad() hypothesis = net(X) cost = loss(hypothesis, y) cost.backward() trainer.step() if epoch % 100 == 0: print(epoch, cost.item()) 0 2.2971670627593994 100 0.5922114849090576 200 0.07867014408111572 300 0.028783854097127914 400 0.016159456223249435 500 0.010870776139199734 600 0.008055186830461025 700 0.006334712728857994 800 0.0051856981590390205 900 0.004369326401501894
0
0
0
浏览量1482
乌兰推

PyTorch入门到项目实战

主要记录如何使用PyTorch实现深度学习笔记,以沐神动手学深度学习内容为基础,介绍如何从最基本的Pytorch应用到构建复杂神经网络模型
0
0
0
浏览量2216
乌兰推

[AIGC] AI图像识别初体验,原来图像识别这么“简单”!

前言在之前,我们已经学过利用AI进行跨境电商分析,虚拟客服和情感分析!今天,我们就来学习如何利用AI进行图像识别!正文必备工具Google账户Colab网站:colab.research.google.com/能正常访问以上网站的途径:魔法AI应用transformers模型 transformers 是一个由Hugging Face公司开发的开源库,用于在自然语言处理(NLP)任务中使用预训练的语言模型。该库提供了一系列预训练的模型,包括了许多流行的模型,如BERT、GPT、RoBERTa等。pipelines派发任务 在Hugging Face的transformers库中,Pipeline是一个方便的工具,用于简化自然语言处理任务的执行。Pipeline提供了一个高级接口,允许你使用一行代码执行各种NLP任务,而无需深入了解底层的模型和处理步骤。 使用Pipeline时,你只需要指定所需任务的名称,然后传递输入文本。Pipeline会自动加载适当的预训练模型、分词器和后处理步骤,使整个过程变得非常简单。"google/owlvit-base-patch32" 图像识别模型PIL图像识别库安装transformers模型!pip install transformers这样就安装成功啦!用pipelin下发任务,导入NLP字典# transformers pipeline 哪些任务? from transformers.pipelines import SUPPORTED_TASKS SUPPORTED_TASKS在 transformers 库中,SUPPORTED_TASKS 是一个包含各种NLP任务及其相关信息的字典。这个字典列出了当前版本的 transformers 支持的不同任务,以及用于执行这些任务的 pipeline 类。如果我们得到!就成功啦!SUPPORTED_TASKS.items() 返回一个包含字典中所有键值对的视图对象,它是一个可迭代对象。在这个特定的情境下,打印 SUPPORTED_TASKS.items() 会输出字典中所有任务及其相关信息的键值对。接下来我们打印一下:print(SUPPORTED_TASKS.items()) # dict 字典 O(1)我们再来解构 SUPPORTED_TASKS 字典的键值对,将键(任务名称)打印出来。输入下述代码# es6 解构 for k, v in SUPPORTED_TASKS.items(): print(k)输出:我们再导入一下pipline下发任务from transformers import pipeline我们再安装一下图片识别模型我们输入这段代码:checkpoint = "google/owlvit-base-patch32"# 图片识别模型 detector = pipeline('zero-shot-object-detection', model=checkpoint)当我们看到这样的界面就说明安装成功咯!导入我们要识别的图片import requests #python的http 请求库 from PIL import Image # PIL python 图片的专业库 url = "https://unsplash.com/photos/oj0zeY2Ltk4/download?ixid=MnwxMjA3fDB8MXxzZWFyY2h8MTR8fHBpY25pY3xlbnwwfHx8fDE2Nzc0OTE1NDk&force=true&w=640" # 人生苦短, 我用python # requests.get img = Image.open(requests.get(url, stream=True).raw) img我们在运行这段代码之后能看到这样一个图,说明运行成功啦!接下来,我们给AI下发识别目标输入下面这段代码:predictions = detector( img, #候选的对象 candidate_labels= ["hat", "sunglasses", "book"] ) predictions我们得到的是一个这样的结果:这段输出有何含义呢?这个输出是零样本目标检测的结果,其中包含了检测到的对象及其相关信息。每个元素都是一个字典,代表一个检测到的对象。'score':表示检测到的对象的得分,范围通常在 0 到 1 之间。得分越高表示算法认为该对象存在的可能性越大。'label':表示检测到的对象的标签或类别,例如 "sunglasses" 表示太阳镜,"book" 表示书,"hat" 表示帽子。'box':是一个包含对象边界框(bounding box)信息的字典,包括了边界框的四个坐标值。'xmin':边界框左上角的 x 坐标。'ymin':边界框左上角的 y 坐标。'xmax':边界框右下角的 x 坐标。'ymax':边界框右下角的 y 坐标。这样,针对输出中的每个元素,我们可以获取对象的得分、标签以及边界框的坐标信息。例如:对于第一个对象(太阳镜): 得分 ('score') 为 0.22562021017074585。标签 ('label') 为 'sunglasses'。边界框 ('box') 的坐标信息为 {'xmin': 349, 'ymin': 228, 'xmax': 429, 'ymax': 265}。类似地,你可以通过遍历输出的每个元素来获取所有检测到的对象的信息。最后:识别图像好啦,到了这一步我们就可以根据我们上面得到的字典进行图像识别了!我们输入这样一段代码:from PIL import ImageDraw # PIL是python图片库 写模块 draw = ImageDraw.Draw(img) for prediction in predictions: box = prediction["box"] label = prediction["label"] score = prediction["score"] xmin, ymin, xmax, ymax = box.values() # 解构 draw.rectangle((xmin, ymin, xmax, ymax),outline="red",width=1) draw.text((xmin,ymin), f"{label}: {round(score, 2)}", fill="red") img那么这段代码有何含义呢?这段使用 Python Imaging Library(PIL)中的 ImageDraw 模块,为图像上的每个目标检测结果绘制边界框和标签。pythonCopy codefrom PIL import ImageDraw draw = ImageDraw.Draw(img)首先,导入了 ImageDraw 类从PIL库中,并使用 ImageDraw.Draw 函数创建了一个用于在图像上绘图的对象 draw。pythonCopy codefor prediction in predictions: box = prediction["box"] label = prediction["label"] score = prediction["score"] xmin, ymin, xmax, ymax = box.values() # 解构然后,通过遍历 predictions,其中每个元素代表一个目标检测结果,获取了每个检测结果的边界框坐标 (box)、标签 (label) 和得分 (score)。使用 box.values() 进行解构赋值,将边界框的坐标值分配给 xmin、ymin、xmax 和 ymax。pythonCopy codedraw.rectangle((xmin, ymin, xmax, ymax), outline="red", width=1) draw.text((xmin, ymin), f"{label}: {round(score, 2)}", fill="red")对于每个检测结果,使用 draw.rectangle() 在图像上绘制一个红色的矩形,表示目标的边界框。使用 draw.text() 在图像上添加标签和得分信息,显示在边界框的左上角。最后给我们呈现的结果就是总结人工智能图像识别在多个领域中都带来了许多好处,利用好这项技术能给我们带来很多好处:自动化处理:AI图像识别可以自动处理大量图像数据,无需人工干预。这使得在短时间内处理大规模图像集合成为可能,提高了效率。2.  快速准确的分类:AI模型能够在瞬间对大量图像进行分类,远远快于人类。并且,当经过足够的训练时,它们通常能够提供高度准确的分类结果。3.  大规模数据处理:大规模图像数据的处理和分析对于许多应用至关重要,例如医学影像分析、卫星图像处理等。AI图像识别能够处理这些数据,从中提取有用的信息。4.  实时识别:对于需要实时决策的应用,如自动驾驶、视频监控等,AI图像识别能够在几乎实时的情况下进行物体识别、行为分析等任务。5. 无人化监控和检测:在安防、生产线等领域,AI图像识别可以用于无人化的监控和检测,提高了安全性和生产效率。6.  个性化服务和推荐:在商业领域,AI图像识别可以用于分析用户的图像,提供个性化的服务和产品推荐,增加用户体验和满意度。7. 医学影像分析:在医学领域,AI图像识别可以用于分析医学影像,辅助医生进行疾病诊断和治疗计划制定。8.  环境监测和保护:在环境科学中,AI图像识别可以用于监测和保护自然环境,例如识别野生动植物、监测气候变化等。
0
0
0
浏览量2016
乌兰推

虚拟客服对话生成-如何利用OpenAI成为"客服超人"

前言在当今的数字时代,人工智能(Artificial Intelligence,简称AI)已经变成了科技界的一领重要探讨话题。AI不仅令人兴奋,还带来了许多前所未有的机会和挑战。​ 今天,我们来学习利用AI完成客服对话的生成!正文我们在之前的文章OpenAI初体验:如何利用OpenAI成为一名跨境电商"超人"就已经初次体验到了OpenAI的魅力,感兴趣的小伙伴,也可以去学习一下!我们大家都知道!随着OpenAI的面世,很多岗位都遭受到了不同程度的冲击!就比如我们今天说到的:客服!现在,直接与我们接触的大部分都是"智能"客服,也就是虚拟客服。虚拟客服是一种基于人工智能(AI)技术的客户服务解决方案,旨在改善客户服务体验、提高效率和减少成本。虚拟客服通常是一个计算机程序或机器人,被设计成模拟人类客服代表的交互和沟通,以回答客户的问题、提供支持、处理投诉和执行其他与客户服务相关的任务。虚拟客服的应用范围非常广泛,包括在线零售、银行和金融服务、电信、航空业、医疗保健、旅游业和更多领域。虚拟客服通常可以在网站、应用程序、社交媒体平台和电话系统上找到。但是呢,虚拟客服只能只能处理一些相对简单的问题,可能无法处理复杂的问题,而且有时候,真人客服与客户之间的交流能更好的了解到客户的情绪和需求!但是,虚拟客服就已经可能解决大部分基本问题了!所以今天就让我们来学习如何利用OpenAI来完成一次客服对话的生成必备工具Google账户OpenAI账号  3.Colab网站:colab.research.google.com/  4.OpenAI网站:openai.com/  5.能正常访问以上网站的途径:魔法  6.AI应用基于大模型 OPENAI的达芬奇 擅长文本处理花销 tokens 算力 API_KEYprompt + openAi 接口调用openai库的Comletion模块实操虚拟客服到底有哪些好处呢?24/7 可用性:虚拟客服可以全天候提供服务,不受时间限制。这意味着客户可以在任何时间联系并获得帮助。自动化和效率:虚拟客服可以自动处理大量的常见问题,从而释放人类客服代表的时间,使他们能够专注于更复杂和有价值的任务。快速响应:虚拟客服通常能够立即响应客户查询,不需要等待或排队。一致性:虚拟客服能够提供一致的信息和支持,而不受情感或主观因素的干扰。成本效益:虚拟客服可以降低客户服务成本,因为它们不需要薪水、培训或福利待遇,与雇佣人类客服代表相比,能够为企业节省资金。接下来,我们开始实操吧!!进行Cobalb模型安装首先我们打开Coblab网站:选择文件:新建笔记本点击+代码然后在我们的代码框中输出下述代码并且运行:!pip install openai==0.10.2我们达到这个图就说明我们的代码运行成功咯!接下来,我们生成一个新代码,输出以下内容#基于openai 的大模型来强化客服能力 import openai openai.api_key="这里输入你的OpenAI中的APIKEY" # 常量 文本生成的模型 COMPLETION_MODEL="text-davinci-003" # 系统后台生成了一条记录,再调用我们这个AIGC来生成客服 # 三个"""可以直接换行,字符串换行可以用字符串模板 prompt="""请你使用朋友的语气回复到客户,并称呼他为亲,他的订单已经发货在路上了,预计在3天之内会送达,订单号2021AEDG,我们很抱歉天气的原因,物流时间比原来长,感谢他选购我们的商品""" # 封装了openai回复的功能 # 这是一个函数 def get_response(prompt, temperature=1.0): # completion模块 # 生成内容 同步的 # 调用openai库的Comletion模块,创建一个新的 # 字典 {k:v} completions = openai.Completion.create( engine = COMPLETION_MODEL, prompt = prompt, max_tokens = 1024, n = 1, # None是关键字Null True stop = None, temperature=temperature ) # JSON choise text print(completions) message = completions.choices[0].text return message 如何获取OpenAI的APIKEY呢?我们已经在前文介绍过啦!那我们还是再学习一遍吧!API-KEY获取进入OpenAI首页选择login再选择API在然后在用户界面选择复制你的密钥,如果没有密钥的可以在下面create new secret key 进行创建注意!!此处ChatGPT的账户中必须有余额!!不然运行的时候会直接报错!!好啦我们接下来回到Colab网页执行客服对话功能代码回到我们刚刚的代码当中#基于openai 的大模型来强化客服能力 import openai openai.api_key="这里输入你的OpenAI中的APIKEY" # 常量 文本生成的模型 COMPLETION_MODEL="text-davinci-003" # 系统后台生成了一条记录,再调用我们这个AIGC来生成客服 # 三个"""可以直接换行,字符串换行可以用字符串模板 prompt="""请你使用朋友的语气回复到客户,并称呼他为亲,他的订单已经发货在路上了,预计在3天之内会送达,订单号2021AEDG,我们很抱歉天气的原因,物流时间比原来长,感谢他选购我们的商品""" # 封装了openai回复的功能 # 这是一个函数 def get_response(prompt, temperature=1.0): # completion模块 # 生成内容 同步的 # 调用openai库的Comletion模块,创建一个新的 # 字典 {k:v} completions = openai.Completion.create( engine = COMPLETION_MODEL, prompt = prompt, max_tokens = 1024, n = 1, # None是关键字Null True stop = None, temperature=temperature ) # JSON choise text print(completions) message = completions.choices[0].text return message我们输入之后点击执行发现,没有效果??欸?为什么是这个样子?因为我们还少了一段代码,执行完上述代码之后,我们在新建一行代码输入下述代码并执行!print(get_response(prompt))我们就可以看到输出结果啦!!{ "choices": [ { "finish_reason": "stop", "index": 0, "logprobs": null, "text": "\u3002\n\n\u4eb2\u7231\u7684\u5ba2\u6237\uff0c\u975e\u5e38\u62b1\u6b49\u4f60\u5728\u7b49\u5f85\u7269\u6d41\u7684\u8fc7\u7a0b\u4e2d\u9047\u5230\u4e86\u9ebb\u70e6\uff0c\u60a8\u7684\u8ba2\u53552021AEDG\u5df2\u7ecf\u53d1\u8d27\u5728\u8def\u4e0a\u4e86\uff0c\u9884\u8ba1\u57283\u5929\u4e4b\u5185\u4f1a\u9001\u8fbe\u3002\u5bf9\u4e8e\u56e0\u5929\u6c14\u539f\u56e0\u5bfc\u81f4\u7269\u6d41\u65f6\u95f4\u5ef6\u957f\u800c\u9020\u6210\u7684\u4e0d\u4fbf\uff0c\u6211\u4eec\u518d\u6b21\u8868\u793a\u6b49\u610f\uff0c\u540c\u65f6\u4e5f\u611f\u8c22\u60a8\u9009\u8d2d\u4e86\u6211\u4eec\u7684\u5546\u54c1\uff0c\u5982\u679c\u6709\u4efb\u4f55\u95ee\u9898\u6b22\u8fce\u968f\u65f6\u548c\u6211\u4eec\u53d6\u5f97\u8054\u7cfb\uff0c\u6211\u4eec\u7aed\u8bda\u4e3a\u60a8\u670d\u52a1\uff01" } ], "created": 1699360572, "id": "cmpl-8IFbQVrBDsBIOX1IQao7epqQIlG5Y", "model": "text-davinci-003", "object": "text_completion", "usage": { "completion_tokens": 283, "prompt_tokens": 169, "total_tokens": 452 }, "warning": "This model version is deprecated. Migrate before January 4, 2024 to avoid disruption of service. Learn more https://platform.openai.com/docs/deprecations" } 。 亲爱的客户,非常抱歉你在等待物流的过程中遇到了麻烦,您的订单2021AEDG已经发货在路上了,预计在3天之内会送达。对于因天气原因导致物流时间延长而造成的不便,我们再次表示歉意,同时也感谢您选购了我们的商品,如果有任何问题欢迎随时和我们取得联系,我们竭诚为您服务! 我需要的客服对话生成,也就实现啦!! 而且内容也相当人性化!
0
0
0
浏览量2012
乌兰推

[AIGC]如何通过Postman对OpenAI发送请求!

引言​ 随着人工智能技术的飞速发展,OpenAI作为领先的人工智能研究机构之一,提供了强大的API,使得开发者能够与先进的自然语言处理模型进行交互。而Postman则作为一款流行的API测试工具,为开发者提供了便捷而直观的方式来测试和调试API。​ 计算机网络是现代科技中不可或缺的一部分,而在网络交互的背后,涉及到了诸多协议和工具。本文将围绕OpenAI API的网络请求展开,介绍如何使用Postman进行图形化的请求操作,并深入理解HTTP协议的层次结构。​ 今天,我们就来学习一下利用Postman进行一次Openai请求模拟!正文OpenAI API的网络请求OpenAI 请求过程的图形化介绍(Postman)OpenAI API的网络请求过程可以通过Postman进行图形化的展示。在Postman中,你可以构建并发送请求,观察请求的各个阶段,包括请求行、头部、请求体等。这使得开发者能够直观地了解整个请求的流程,有助于调试和优化。Postman:HTTP请求制造工具Postman是一款强大的HTTP请求制造工具,它提供了直观的界面和丰富的功能,使得开发者能够轻松构建、测试和调试API请求。通过Postman,你可以模拟各种HTTP请求,包括GET、POST等,以便更好地理解和管理网络交互。必备工具注册OpenAI账户: 如果你还没有OpenAI账户,需要首先注册并创建一个项目。在项目中生成API密钥,用于在Postman中进行身份验证。 在这里不懂的可以参考:OpenAI初体验:如何利用OpenAI成为一名跨境电商"超人"-小白篇 安装Postman: 下载并安装Postman应用程序,注册一个可用的Postman账户。在这里大家可以直接在浏览器搜索下载Postman,注册账户的过程的相当简单!按照提示绑定自己的邮箱即可!注意注意!!!​我们这次尝试需要使用“魔法”,否则是进行不了的。​至于魔法是什么,大家可以网络搜索!有很多大神的教程可以供大家参考!!我们接下来就步入正题!实操第一步:我们打开POSTMAN,并且按照图示操作第二步,在点击加号之后的界面按照图示选择第三步,我们拿到我给的URL,输入到图示的框中! https://api.openai.com/v1/completions 第四步,尝试运行我们在这里尝试运行,它会给我们提示“error”,我们学会自己查看错误,看看它给我们说了什么!直接给大家上翻译啦!大概的意思是:"出错了,你没提供 API 密钥。你需要在 Authorization 头部中使用 Bearer 认证(比如 Authorization: Bearer YOUR_KEY),或者如果你是从浏览器访问 API 并被提示输入用户名和密码,你可以将 API 密钥放在密码字段中(用户名留空)。哦!原来是没有配置API密钥!第五步,配置API密钥第六步,再次运行我们再点击一下Send!我们发现还是反馈了一个错误,我们再来看看这个错误是什么意思!"出错了,你必须提供一个模型参数。"看来,我们还需要一个模型参数!!第七步,提供模型参数我先把模型参数代码提供给大家:{ "model": "text-davinci-003", "prompt": "你好,吃饭了嘛?", "temperature": 0.2 }"model": "text-davinci-003":指定了要使用的 GPT-3 模型,这里是 "text-davinci-003",它是 GPT-3 的一个文本生成模型,以支持自然语言处理任务。"prompt": "你好,AI图像识别":这是一个提示(prompt),用于启动模型生成文本的过程。我们给的例子中,模型将基于给定的提示生成文本,就像是对话的开头。"temperature": 0.2:这是一个控制生成文本多样性的参数,它的值介于0到1之间。较低的温度值(如0.2)会使生成的文本更加确定性,而较高的温度值则会增加文本的随机性。在这里,0.2 表示生成的文本相对稳定和一致。我们按照图示操作:最后,运行请求是不是很简单呢?总结:知识点!!!模拟OpenAI API请求的代码层运行在代码层,OpenAI API的请求可以通过OpenAI官方提供的Python库实现。例如,使用openai.Completion.create()方法可以发起生成文本的请求。这一层次的运行通常涉及本地调试和项目服务器运行。运行在物理层(你的电脑)在物理层,OpenAI API请求涉及到网络传输。数据从你的电脑出发,经过物理链路传输至OpenAI的服务器。LLM OpenAI 的服务器集群OpenAI的服务器集群位于网络层次结构的更深层。这是OpenAI处理请求、运行模型的实际场所。协议层的窥探:HTTPS互联网超文本传输协议在协议层,我们面对的是HTTP(HyperText Transfer Protocol)协议。而在OpenAI的网络请求中,我们使用的是HTTPS,即HTTP的安全版本。HTTPS有七层,最底层有IP地址,是Web通信的基础。OpenAI封装了HTTP请求库,使得开发者无需过多关注底层细节。HTTP 状态码的重要性在HTTP协议中,状态码用于表示服务器对请求的处理结果。常见的状态码有:1xx:请求还在进行中2xx:成功3xx:跳转4xx:客户端错误,如未授权(401)5xx:服务器错误HTTP是一种基于请求响应的简单协议,其请求行包含了请求的方法(GET、POST等)和URL。头部包含了关键信息,如授权信息。请求体则携带了具体的数据。OpenAI 模型和HTTP请求在OpenAI中,使用Davinci等模型进行请求时,如果请求不合法,服务器将返回400 Bad Request。这体现了HTTP协议在模型请求中的重要性。JSON 数据交换的标准在HTTP请求中,数据交换通常采用JSON(JavaScript Object Notation)格式。这成为数据交换的标准,使得请求和响应之间的数据结构清晰而统一。Post请求的方法HTTP中有多种请求方法,其中POST方法用于向服务器提交数据。在OpenAI中,通过POST请求,我们能够将生成文本的请求发送给OpenAI的服务器。
0
0
0
浏览量504
乌兰推

[AIGC]-利用OpenAI进行NLP情感分析-情感大师(进阶版)

前言​ 我们在之前的文章,初次体验了OpenAIOpenAI初体验:如何利用OpenAI成为一名跨境电商"超人"-小白篇我们也在之前文章中,学会了客服对话的生成:[AIGC->OpenAI体验]虚拟客服对话生成-如何利用OpenAI成为"客服超人"在上一篇,我们介绍了利用AIGC进行关键词情感分析!:[AIGC]-OpenAI情感分析-如何利用OpenAI成为“情感大师”? ​ 今天,我们在昨天的基础上来学习利用AI完成关键词情感分析(进阶版)!正文必备工具Google账户OpenAI账号Colab网站:colab.research.google.com/OpenAI网站:openai.com/能正常访问以上网站的途径:魔法AI应用openai 0.10.2 版本text-davinci-003模型OpenAI-ApiKeyOpenAI->completion模块介绍主要模块OpenAI 0.10.2版本是一个较早的GPT-3模型版本,发布于2020年10月。这个版本的功能包括:强大的自然语言生成能力:GPT-3模型在自然语言生成方面具有很强的能力,能够根据用户的输入生成流畅、连贯、符合语法规则的文本。支持多种语言:OpenAI 0.10.2版本支持多种语言,包括英语、西班牙语、法语、德语、意大利语等。文本生成与修改:GPT-3模型不仅可以生成全新的文本,还可以对已有的文本进行修改和优化,比如自动修正语法错误、优化文章结构等。实时翻译:GPT-3模型还支持实时翻译,可以将输入的文本翻译成其他语言,并生成对应的译文。文本分类和情感分析:GPT-3模型还可以对文本进行分类和情感分析,比如判断一段文本属于哪个类别或者表达的情感是正面还是负面。其他应用:除了以上功能,OpenAI 0.10.2版本还可以用于其他应用,比如自动摘要、问答系统等。Text-Davinci-003模型是GPT-3系列中的最新型号,也是目前功能最强大的GPT-3模型之一。它具有以下功能:生成高质量的写作:Text-Davinci-003可以生成更清晰、更有吸引力、更引人注目的内容,帮助开发人员开发出更好的应用程序。处理更复杂的需求指令:与之前的GPT-3模型相比,Text-Davinci-003可以处理更复杂的需求指令,使开发人员能够更好地发挥创意,实现更复杂的应用程序交互和多步操作。支持生成较长形式的内容:Text-Davinci-003支持生成较长形式的内容,如长篇文章、技术文档等,可以帮助开发人员执行以往可能难以完成的任务。文本插入完成功能:Text-Davinci-003添加了文本插入完成功能,除了运用前缀提示之外,还增加使用后缀提示,可用于编写长文本、段落过渡和遵循大纲等任务中。适用于程序代码完成:Text-Davinci-003也可以适用于程序代码完成,可用于补完函数或是文件。实操1. 进行Cobalb模型安装首先我们打开Coblab网站:选择文件:新建笔记本点击+代码然后在我们的代码框中输出下述代码并且运行:# openai 情感分类 更牛逼 Completions !pip install openai==0.10.2 #指定版本这样就算安装成功了2. 使用OpenAI的API来创建一个自然语言生成模型如果大家还有不懂怎么使用OpenAI的API的同学可以前往:OpenAI初体验:如何利用OpenAI成为一名跨境电商"超人"-小白篇 - 掘金 (juejin.cn)学习!我们输入下面的代码来创建自然语言生成模型:import openai COMPLETION_MODEL = 'text-davinci-003' openai.api_key='这里输入你获取的OpenAi的APIKEY值' def get_response(prompt, temperature=1.0): # completion模块 # 生成内容 同步的 # 调用openai库的Comletion模块,创建一个新的 # 字典 {k:v} completions = openai.Completion.create( engine = COMPLETION_MODEL, prompt = prompt, max_tokens = 1024, n = 1, # None是关键字Null True stop = None, temperature=temperature ) #JSON choices txt print(completions) message = completions.choices[0].text return message这里,如果我们成功运行就表示自然语言模型生成成功啦!3. 输入情感分析案例进行情感识别我们输入下列代码:# openai 开发范式 # NLP transformers coding->openai prompt engineer # prompt 设计 精细化的需求,举例 数据 prompts = """判断一下用户的评论情感上是正面的还是负面的 评论:买的银色版真的很好看,一天就到了,晚上就开始拿起来完系统很丝滑流畅,做工扎实,手感细腻,很精致哦苹果一如既往的好品质 情感:正面 评论:随意降价,不予价保,服务态度差 情感:负面 """ # 吴恩达prompt engineer good_case = prompts +""" 评论:外形外观:苹果审美一直很好,金色非常漂亮拍照效果:14pro升级的4800万像素真的是没的说,太好了,运行速度:苹果的反应速度好,用上三五年也不会卡顿的,之前的7P用到现在也不卡其他特色:14pro的磨砂金真的太好看了,不太高调,也不至于没有特点,非常耐看,很好的情感: 情感: """这段代码是关于OpenAI和NLP(自然语言处理)的示例,展示了如何使用OpenAI进行NLP任务,特别是针对文本的情感分析。Prompt Engineer方法是一种通过预先定义好的提示来引导模型进行特定任务的方法。在这个例子中,我们用到了“判断一下用户的评论情感上是正面的还是负面的”这个提示来引导模型对给定的评论进行情感分类。在“good_case”字符串中,我们看到了一个类似的prompt,但这次它没有直接给出情感,而是要求模型根据评论来预测情感。这个prompt被用来测试模型是否能够正确地根据评论预测出正确的情感。我们来看看输出结果:print(get_response(good_case))输出: { "choices": [ { "finish_reason": "stop", "index": 0, "logprobs": null, "text": "\u6b63\u9762" } ], "created": 1699531125, "id": "cmpl-8IxyH3GYn1R6hGuJs1wDgzhDqnEPq", "model": "text-davinci-003", "object": "text_completion", "usage": { "completion_tokens": 5, "prompt_tokens": 533, "total_tokens": 538 }, "warning": "This model version is deprecated. Migrate before January 4, 2024 to avoid disruption of service. Learn more https://platform.openai.com/docs/deprecations" } 正面这样,我们就利用了OpenAI对一段话进行情感判断!
0
0
0
浏览量2017
乌兰推

[AIGC] LangChain 框架--OpenAI目前最“强”框架入门

今天,我们来带大家学习一个强大的OpenAI框架LangChain,在以往的开发过程当中,我需要一个一个模型进行调用,而有了LangChain,能够为我们省去至少1/2的工作量!一、LangChain小白入门简介!LangChain是一个强大的框架,旨在帮助开发人员使用语言模型构建端到端的应用程序,它提供了一套工具、组件和接口,可简化创建由大型语言模型(LLM)和聊天模型提供支持的应用程序的过程。LangChain作为一个语言模型集成框架,其使用案例与语言模型的使用案例大致重叠,包括文档分析和摘要、聊天机器人和代码分析。它提供了灵活的抽象和AI优先的工具,可帮助开发人员将LLM应用程序从原型转化为生产环境。通过LangChain我们可以实现例如:基于文档数据的问答聊天机器人代码分析等等功能。二、OpenAI简介相关玩AIGC的各位大佬,对OpenAI已经相当哇塞了!这里我还是为大家简单介绍一遍!OpenAI(Open Artificial Intelligence)是一个致力于推动人工智能(AI)研究和开发的研究实验室。大家所熟知的ChatGPT-3.5,ChatGPT-4等等模型都是其产品!我们可以通过一个OpenAI提供的Api-Key来对这些模型进行调用!三、LangChain开发必备谷歌账号--用于登录Colab(欢迎使用 Colaboratory - Colaboratory (google.com)) Google Colab(Colaboratory)是一款由Google提供的免费云端Python编程环境。通过Colab,你可以在任何地方、任何设备上使用浏览器运行和编写Python代码,而无需担心本地环境的设置和配置。它的界面基于Jupyter Notebooks,这种交互式文档格式让你能够在代码中嵌入文字、图像,从而更好地组织和分享你的工作。 Colab不仅提供免费的云计算资源,包括GPU和TPU,还内置了许多常用的Python库和框架,如NumPy、Pandas、Matplotlib,以及流行的深度学习框架TensorFlow和PyTorch。这使得Colab成为进行数据分析、机器学习实验和深度学习模型训练的理想选择。2.  OpenAI账号--用于获取Api-key调用OpenAI模型(注意查看自己的账号是否还有余额,不然会报错!)四、安装指令1、安装LangChain框架!pip install langchain==0.0.316首先安装的是版本号为0.0.316的langchain模型! 安装界面如下图所示!2、安装OpenAI模型!pip install openai==0.28.1**注意!!!**这里我们一定要指定版本为0.28.1,如果没有指定安装的版本,会默认安装最新版,而最新版不兼容!所以我们要指定OpenAI的代码,才能完成我们的后续操作!!安装界面如下图所示:3、版本号知识普及例如0.0.316面试官:0.0.316是什么意思?版本号通常由三个数字组成,形式为 X.Y.Z。在版本号 "0.0.316" 中:X: 主版本号。当进行重大更改或不向后兼容的更改时,递增 X。Y: 次版本号。当添加新功能或进行向后兼容的更改时,递增 Y。Z: 补丁版本号。通常是修复错误或进行其他小的改进时递增 Z。"0.0.316" 是一个非常早期或者是一个处于开发阶段的版本号。在大多数情况下,主版本号为零表示产品可能处于早期开发或测试阶段,而不稳定或者可能会发生重大更改。还没有正式公测。五、运行代码!from langchain.chat_models import ChatOpenAI from langchain.schema import HumanMessage import os os.environ['OPENAI_API_KEY']='你的有效的OepnAI Api key' chat = ChatOpenAI(temperature=0,model_name="gpt-3.5-turbo") response = chat([HumanMessage(content="Hello LangChain!")]) print(response)输入完上述代码之后,你应该看到这样的一个回复:content='Hello! How can I assist you today?'我接下来为大家分析一下这段代码:from langchain.chat_models import ChatOpenAI:从 langchain 包的 chat_models 模块中导入名为 ChatOpenAI 的类。from langchain.schema import HumanMessage:导入一个聊天模型,指定一个角色 我们是HumanMessage OpenAI是systemimport os引入一下文件系统os.environ['OPENAI_API_KEY']='你的有效的OepnAI Api key':设置环境变量,LangChain自动来拿chat = ChatOpenAI(temperature=0,model_name="gpt-3.5-turbo"):temperature=0自由度为0,模型名称 (model_name)为"gpt-3.5-turbo"response = chat([HumanMessage(content="Hello LangChain!")]):将一条人类的消息("Hello LangChain!")传递给 chat 对象,然后获取由 Chat 模型生成的响应,将其存储在 response 变量中。这可以用于进一步处理或显示模型生成的聊天响应。print(response):这个就很简单啦!输出一下OpenAI的响应!接下来,我们就可以这样去与我们的模型进行对话了!response = chat([HumanMessage(content="我怎么样赚够一个亿")]) print(response)我们会收到这样一个回复:content='要赚够一个亿,需要具备以下几个方面的条件和策略:\n\n1. 设定明确的目标:确定赚取一个亿的具体时间和方式,制定详细的计划和步骤。\n\n2. 提高自身能力:通过学习、培训和积累经验,提升自己的技能和知识,增加自身的竞争力。\n\n3. 创业或投资:可以选择创业或投资来实现财富积累。创业需要有创新的商业模式和市场需求,投资需要有正确的投资策略和风险控制能力。\n\n4. 多元化投资:将资金分散投资于不同的领域和项目,降低风险,提高回报。\n\n5. 深入了解市场:研究市场趋势和行业发展,找到有潜力的投资机会和商业机会。\n\n6. 节约和理财:合理规划和管理个人财务,控制开支,积极进行理财投资,增加财富积累。\n\n7. 建立人脉关系:与成功人士建立联系和合作,借助他们的经验和资源,获取更多的机会和支持。\n\n8. 坚持和耐心:赚取一个亿需要时间和努力,要有坚定的信念和持之以恒的精神。\n\n请注意,以上仅为一般性建议,具体的赚取一个亿的方法和策略需要根据个人情况和市场环境进行调整和实施。'大家学会了吗?这个框架还有更多用法,大家可以自己去探索一下!
0
0
0
浏览量1302
乌兰推

OpenAI初体验:如何利用OpenAI成为一名跨境电商"超人"-小白篇

前言今天我们来学习一下,如何利用OpenAI成为一名亚马逊跨境电商超人!小白课:OpenAI初体验!必要工具:Google账户OpenAI账号Colab网站:colab.research.google.com/OpenAI网站:openai.com/能正常访问以上网站的途径AI应用 基于大模型 OPENAI的达芬奇 擅长文本处理花销 tokens 算力 API_KEYprompt + openAi 接口调用返回的数据是:JSON 继续业务正文跨境电商 :相信这个名词大家一定不陌生吧!甚至有的大佬已经成为了这个行业的一员,今天我们就利用这个项目,来一次OpenAI初体验!科普一下!跨境电商是指通过互联网平台在不同国家和地区之间进行商品交易的商业活动。它使得消费者可以直接从国外购买商品,而不需要经过传统的代购或中间商。跨境电商的发展得益于全球化和数字化的发展趋势,以及互联网、移动设备和支付技术的普及。想要成为一名优秀的**”AI商人“**,我们该如何做?调研市场,找到自己想要的商品售卖!AI 代码 Translate价格 Price  OpenAI定价写文案 openAI如此,我们只需要找到要售卖的商品即可!也就是说,我们可以利用OpenAI将我们的大部分工作完成!!现在大家的脑子想的恐怕都是搞钱搞钱!OpenAI被认为揽括了全网的知识,这主要归功于其强大的自然语言处理(NLP)能力和庞大的语料库。首先,OpenAI是一家以研究强化学习和人工智能技术为目标的智能研究机构,由业界知名人士Elon Musk, Sam Altman, Greg Brockman等共同创立,旨在探索人工智能领域最先进的技术。它不仅拥有先进的技术和算法,而且得到了大量数据的支持。这些数据来自于互联网上的各种资源,包括网页、文章、新闻、论坛、博客等。通过将这些数据转化为训练集和测试集,OpenAI得以对模型进行训练和优化,进而实现精准的文本分类、实体识别、情感分析等功能。其次,OpenAI还拥有一个庞大的语料库,包含了大量不同领域的知识。这个语料库不仅包括英文文本,还包括其他语言的文本,如中文、法文、德文等。这使得OpenAI能够处理不同语言的知识,并且能够从大量的文本中提取有用的信息。此外,OpenAI还通过与外部知识库和信息源的合作,不断更新和扩展其知识库。这意味着OpenAI不仅能够处理现有的知识,还能够随时更新和适应新的知识和信息。因此,利用OpenAI为跨境电商进行服务,将会提供无与伦比的便利!接下来我们开始教程!首先打开OpenAI网页我们就以遥控汽车儿童玩具为例:在OpenAI中输入这样一段文字Consideration product:工厂现货遥控汽车儿童玩具 1. Compose human readable product title used on Amazon in english within 20 words 2. Write 5 selling points for the products in Amazon 3. Evaluat a price range for this product in U.S 4. Output the result in json format with three properties called title,selling_points and price_range 翻译: 考虑产品:工厂现货遥控汽车儿童玩具 1.在20个单词内用英语编写亚马逊上使用的人类可读的产品标题 2.为亚马逊3中的产品写下5个卖点。 3.评估该产品在美国的价格范围 输出结果为JSON格式,具有三个名为标题、销售点和价格范围的属性 OpenAI会给我们返回一个这样的 反馈:接下来,我们打开Colab网站:选择红色框中的文件打开新建笔记本输入红色方框的中的代码之后,点击红色方框中的运行键运行代码出现这样的提示就表示你已经安装成功!!!!接下来,我们回到OpenAI中!选择login再选择API在然后在用户界面选择复制你的密钥,如果没有密钥的可以在下面create new secret key 进行创建注意!!此处ChatGPT的账户中必须有余额!!不然运行的时候会直接报错!!接下来,回到Colab网站,点击红框中的 + 代码 ,然后再在下面的红框中输入代码!!接下来代码这样写:import openai openai.api_key = "这里输入你你复制的OpenAI密钥" #openai 的sdk COMPLETION_MODEL = "text-davinci-003"#模型常量 达芬奇 自动完成 生成内容 # 字符串模板 #换行 #描述的细节需求 #分布去执行 #输出的格式 prompt = """ Consideration product:工厂现货遥控汽车儿童玩具 1.Compose human readable product title used on Amazon in english within 20 words 2. Write 5 selling points for the products in Amazon 3. Evaluat a price range for this product in U.S. Output the result in json format wiht three properties called title, selling_points and price_range """ # def定义一个函数 封装和ChatGPT聊天的内容 def get_response(prompt):#冒号表示内容的开始 completions = openai.Completion.create( #大模型很值钱 engine = COMPLETION_MODEL,#模型 选择模型 prompt = prompt,#获取描述的东西 提示词 max_tokens = 512,#控制算力,省点钱 n = 1,#返回一条结果 stop=None,#不要停下来 temperature=0.0 #自由发挥度0-2之间,值越大越随意 ) # print(completions) message = completions.choices[0].text # return message print(get_response(prompt)) # { # title: "", # selling_points:"", # price_range: , # } 最后一步就是运行我们的代码啦!这样我们就会得出这样的结果:{ "title": "Remote Control Kids Toy Car - Factory Stock", "selling_points": [ "Realistic design with working headlights and taillights", "Easy to use remote control with two speed settings", "High quality materials for durability and safety", "Rechargeable battery for extended playtime", "Suitable for children ages 3 and up" ], "price_range": "$20 - $50" }翻译: {。 “标题”:“遥控儿童玩具车-工厂库存”, “卖点”:[。 “前大灯和尾灯工作时的逼真设计”, “具有两种速度设置的遥控器使用方便”, “经久耐用、安全可靠的优质材料”, “可充电电池,延长游戏时间”, “适合3岁及以上儿童” ],。 “Price_range”:“$20-$50” } 这样,我们就直接得到了,我们电商需要的标题!卖点!和价格!接下来就是搞钱搞钱搞钱啦!上文为OpenAI体验案例,具体实际,还请根据现实出发!最后,我们的OpenAI电商初体验就到此结束啦!!相信大家也已经见识到了AI的强大之处!还有更多的功能等待大家去探索!
0
0
0
浏览量2028
乌兰推

[AIGC]-OpenAI情感分析-如何利用OpenAI成为“情感大师”?

前言​        OpenAI的智能远远超乎我们的想象。​ 过去,我们一直认为,AI只是一个有感情的对话机器,但是,现如今我们是时候改变这种想法了!在对大模型不断地训练积累下,我们的AI已经能够实现对话感情的分析,和具有"感情"的回复了​ 今天,我们来学习利用AI完成关键词情感分析!正文我们在之前的文章OpenAI初体验:如何利用OpenAI成为一名跨境电商"超人"-小白篇 就已经初次体验到了OpenAI的魅力,感兴趣的小伙伴,也可以去学习一下!我们也在上一篇文章中,学会了客服对话的生成:[AIGC->OpenAI体验]虚拟客服对话生成-如何利用OpenAI成为"客服超人"什么是AI情感分析呢?AI情感分析是一种人工智能技术,旨在识别和理解文本、语音或图像中的情感和情感表达。它有时也被称为情感检测、情感识别或情感计算。这项技术的主要目标是分析和解释人们在各种通信渠道中表达的情感,以便帮助企业和组织更好地理解其受众的情感状态、情感需求和情感反应。就比如AI能够通过你写的句子中的一些关键字来判断你这句话中蕴含的情感!这个技术的应用领域也相当广泛:社交媒体分析:AI情感分析可用于监测社交媒体上的情感趋势,以理解公众对某一话题、品牌或事件的情感反应。客户服务:企业可以使用情感分析来检测客户在服务互动中的情感,以改善客户满意度。市场调查:市场研究人员可以利用情感分析来识别产品或服务的潜在市场反应。舆情分析:政府和新闻机构可以使用情感分析来追踪公众对新闻事件的情感反应。今天,我们就来学习一下如何利用AI分析关键词情感!必备工具Google账户OpenAI账号Colab网站:colab.research.google.com/OpenAI网站:openai.com/能正常访问以上网站的途径:魔法AI应用Transformer模型 --来自于抱抱脸pipeline模块sentiment-analysis情感分析分类器uer/roberta-base-finetuned-dianping-chinese中文情感分析模型实操技术方法:AI情感分析使用自然语言处理(NLP)和机器学习技术,例如文本分类、情感词汇识别、情感强度分析等,来检测和量化文本中的情感。对于语音和图像情感分析,声音特征和图像处理技术也会得到应用。情感分类:情感分析通常将文本或内容分类为积极、消极或中性。有些系统可以提供更详细的情感分类,如愤怒、快乐、悲伤、惊讶等。挑战和限制:情感分析仍然面临一些挑战,例如文本的复杂性、多义性、文化差异、语言模型的偏见以及跨文化和跨语言的情感理解。商业价值:AI情感分析可以帮助企业改进客户服务、制定市场策略、提高产品质量、发现消费者需求,以及更好地了解受众的需求和期望。进行Cobalb模型安装首先我们打开Coblab网站:选择文件:新建笔记本点击+代码然后在我们的代码框中输出下述代码并且运行:!pip install transformers #来自抱抱脸如果我们得到这样一个结果的话:我们达到这个图就说明我们的代码运行成功咯!接下来,我们生成一个新代码,输出以下内容派发一个新的情感分析模块from transformers import pipeline #pipeline 派发模块 classifer = pipeline('sentiment-analysis') # 情感分析得到这样的结果,就说明安装成功啦!!执行情感分析代码我们让我们的模型分析一下i love you的情感result = classifer('i love you') result输出:[{'label': 'POSITIVE', 'score': 0.9998656511306763}]这样我们的输出结果就变成这样,情感是积极的,分数我们可以当个参考我们再来分析一下shut up的情感!result = classifer('shut up') result[{'label': 'NEGATIVE', 'score': 0.9992936849594116}]我们这个词分析的结果就消极的!!没有问题!我们再来分析一下“遥遥领先”result = classifer('遥遥领先') result[{'label': 'NEGATIVE', 'score': 0.8616330027580261}]我们可以发现这里的输出结果出了问题?这是为什么?因为我们没有安装uer/roberta-base-finetuned-dianping-chinese模型python复制代码#中文模型 大众点评的亿万条数据训练出来的 classifer = pipeline('sentiment-analysis', model="uer/roberta-base-finetuned-dianping-chinese")#下发任务,能够理解中文这样就安装成功啦, 接下来,我们再识别一下“遥遥领先”result = classifer("遥遥领先") print(result)输出:[{'label': 'positive (stars 4 and 5)', 'score': 0.941333532333374}]好了,这样我们的输出的结果!就没有问题啦!!!
0
0
0
浏览量2010
乌兰推

AIGC概念学习

深入学习[AIGC] LangChain框架、Postman与OpenAI请求,探索AI图像识别和情感分析的基础与进阶应用。从虚拟客服到跨境电商,全面了解如何利用OpenAI成为多领域的“超人”。
0
0
0
浏览量2179

履历