OpenCL编程指南-3.2OpenCL上下文

news2024/11/19 9:28:19

OpenCL上下文

上下文是所有OpenCL应用的核心。上下文为关联的设备、内存对象(例如,缓冲区和图像)以及命令队列(在上下文和各设备之间提供一个接口)提供了一个容器。正是上下文驱动着应用程序与特定设备以及特定设备之间的通信,为此OpenCL定义了内存模型。例如,内存对象分配有一个上下文,不过可以由特定的设备来更新,OpenCL 的内存保证相同上下文中的所有设备可以在明确定义的同步点看到这些更新。

有一点很重要,要认识到尽管通常这些阶段可以构成OpenCL程序的基础,不过完全可以使用多个上下文,分别由不同平台创建,并把工作分布到这些上下文和关联的设备上。区别在于,OpenCL的内存模型不会跨设备,这说明内存对象不能由不同的上下文(可能由相同或不同的平台创建)共享。这也意味着,需要在上下文之间共享的数据必须手动在上下文间移动。这个概念如图3-1所示。
在这里插入图片描述
通常平台和设备会在程序或库的开始位置查询,与之不同,你可能希望在程序运行过程中更新上下文,或者分配或删除内存对象等。一般地,应用程序会这样使用OpenCL:
1)查询有哪些平台。
2)查询各个平台支持的设备集:
使用clGetDeviceInfo()为特定功能选择设备。
3)由选择的设备创建上下文(必须由一个平台的设备创建各个上下文),然后利用上下文可以做到:
a.创建一个或多个命令队列。
b.创建程序,使它在一个或多个关联设备上运行。
c.从这些程序创建一个内核。
d.在宿主机或设备上分配内存缓冲区和图像。
e.将数据写至或复制到特定设备,或者由设备写数据。
f.将内核(设置适当的参数)提交到命令队列来执行。

给定一个平台和一组关联设备,可以用命令clCreateContext()创建一个OpenCL 上下文,如果有平台和设备类型,可以使用clcreateContextFromType()创建上下文。这两个函数声明为:

extern CL_API_ENTRY cl_context CL_API_CALL
clCreateContext(const cl_context_properties * properties,
                cl_uint              num_devices,
                const cl_device_id * devices,
                void (CL_CALLBACK * pfn_notify)(const char * errinfo,
                                                const void * private_info,
                                                size_t       cb,
                                                void *       user_data),
                void *               user_data,
                cl_int *             errcode_ret) CL_API_SUFFIX__VERSION_1_0;


extern CL_API_ENTRY cl_context CL_API_CALL
clCreateContextFromType(const cl_context_properties * properties,
                        cl_device_type      device_type,
                        void (CL_CALLBACK * pfn_notify)(const char * errinfo,
                                                        const void * private_info,
                                                        size_t       cb,
                                                        void *       user_data),
                        void *              user_data,
                        cl_int *            errcode_ret) CL_API_SUFFIX__VERSION_1_0;

这会创建一个OpenCL上下文。参数properties的可取值如下。

CL_CONTEXT_PLATFORM   cl_platform_id     指定要使用的平台

这里列出的属性只是与上下文关联的平台。其他上下文属性用特定的OpenCL扩展定义。参数 devicesdevice_type分别允许显式地指定设备集或者限制为特定的设备类型。参数 pfn_notifyuser_data用来共同定义一个回调,可以调用这个回调报告上下文生命期中所出现错误的有关信息,要把user_data作为最后一个参数传至回调。

给定一个平台,下面的例子展示了如何查询GPU设备集,如果有一个或多个设备,还可以创建一个上下文。

cl_platform pform;
size_t num;
cl_device_id *devices;
cl_context context;
size_t size;

clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, 0, NULL, &num);
if (num > 0)
{
     devices = (cl_device_id *)alloca(num);
     clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, num, &devices[0], NULL);
}
cl_context_properties properties[] = 
{
     CL_CONTEXT_PLATFORM, (cl_context_properties)platform, 0
};

context = clCreateContext(properties, size/sizeof(cl_device_id), devices, NULL, NULL, NULL);

给定一个上下文,可以用以下命令查询各个属性:

extern CL_API_ENTRY cl_int CL_API_CALL
clGetContextInfo(cl_context         context,
                 cl_context_info    param_name,
                 size_t             param_value_size,
                 void *             param_value,
                 size_t *           param_value_size_ret) CL_API_SUFFIX__VERSION_1_0;

这个命令返回OpenCL上下文的特定信息。参数param_name定义了合法的查询:

CL_CONTEXT_REFERENCE_COUNT    cl_uint                  返回上下文引用计数
CL_CONTEXT_NUM_DEVICES        cl_uint                  返回上下文中的设备数
CL_CONTEXT_DEVICES            cl_device_ia[]           返回上下文中的设备列表

CL_CONTEXT_PROPERTIES         cl_context_properties[]  返回clCreatecontext或clcreatecontextFromType中指定的properties参数。
                                                       如果用来创建上下文的clCreateContext或clCreateContextFromType中指定的properties参数不为NULL,
                                                       则这个实现必须返回properties参数中指定的值;
                                                       如果用来创建上下文的clCreateContext或c1CreatecontextFromType中指定的properties参数为NULL,
                                                       则实现可能返回param_value_size_ret为0
                                                       (也就是说,没有要返回的上下文属性值),
                                                       或者在param_value指向的内存中返回上下文属性值0(用来终止上下文属性列表)

下面的例子展示了如何使用clGetContextInfo()查询一个上下文,得到关联设备列表:

cl_uint numPlatforms;
cl_platform_id *platformIDs;
cl_context context = NULL;
size_t size;

clGetPlatformIDs(0, NULL, &numPlatforms);
platformIDs = (cl_platform_id *)alloca(sizeof(cl_platform_id) * numPlatforms);

clGetPlatformIDs(numPlatforms, platformIDs, NULL);

cl_context_properties properties[] = 
{
    CL_CONTEXT_PLATFORM, (cl_context_properties)platformIDs[0], 0
};

context = clCreateContextFromType(properties, CL_DEVICE_TYPE_ALL, NULL, NULL, NULL);

clGetContextInfo(context, CL_CONTEXT_DEVICES, 0, NULL, &size);

cl_device_id *devices = (cl_device_id*)alloca(sizeof(cl_device_id) * size);

clGetContextInfo(context, CL_CONTEXT_DEVICES, size, devices, NULL);

for (size_t i = 0; i < size / sizeof(cl_device_id); i++)
{
      cl_device_type type;
      clGetDeviceInfo(devices[i], CL_DEVICE_TYPE, sizeof(cl_device_type), &type, NULL);
      switch(type)
      {
           case CL_DEVICE_TYPE_GPU:
                    std::cout << "CL_DEVICE_TYPE_GPU" << std::endl;
                    break;
           case CL_DEVICE_TYPE_CPU:
                    std::cout << "CL_DEVICE_TYPE_CPU" << std::endl;
                    break;
           case CL_DEVICE_TYPE_ACCELERATOR:
                    std::cout << "CL_DEVICE_TYPE_ACCELERATOR" << std::endl;
                    break;
      }
}

在ATI Stream SDK上,对于有一个Intel i7 CPU设备和ATI Radeon 5780的机器,会显示以下结果:

CL_DEVICE_TYPE_CPU
CL_DEVICE_TYPE_GPU

类似于所有OpenCL对象,上下文是引用技术,可以用以下两个命令递增和递减引用数:

cl_int clRetainContext(cl_context context)
cl_int clReleaseContext(cl_context context)

这两个命令会分别将一个上下文的引用计数递增和递减。

最后我们构建了一个简单的例子,它会完成一个输入信号的卷积。卷积是很多信号处理应用中都会出现的常见操作,最简单的形式是将-个信号(输入信号)与另一个信号(模板)结合生成一个最终输出(输出信号)。卷积对于OpenCL是一个非常好的应用。通过卷积可以展示大量输入的大规模数据并行性,而且有很好的数据环境,允许使用OpenCL的共享构造。

图3-2显示了将一个3×3模板应用到一个8×8输入信号的过程,最后会得到一个6×6的输出信号。这个算法很简单,输出信号的每个样本如下生成:
1)将模板置于输入信号上,以相应的输入位置为中心。
2)将输入值与模板中的相应元素相乘。
3)将第2步的结果累加为一个和,写至相应的输出位置。
在这里插入图片描述
对于输出信号中的各个位置,根据下面代码,由内核convolve完成前面的步骤。也就是说,各个输出结果可以并行计算。

Convolution.cl

__kernel void convolve(
	const __global  uint * const input,
    __constant uint * const mask,
    __global  uint * const output,
    const int inputWidth,
    const int maskWidth)
{
    const int x = get_global_id(0);
    const int y = get_global_id(1);

    uint sum = 0;
    for (int r = 0; r < maskWidth; r++)
    {
        const int idxIntmp = (y + r) * inputWidth + x;

        for (int c = 0; c < maskWidth; c++)
        {
			sum += mask[(r * maskWidth)  + c] * input[idxIntmp + c];
        }
    } 
    
	output[y * get_global_size(0) + x] = sum;
}

下面代码包含这个简单例子的宿主机代码。主函数首先查询可用的平台列表,然后使用clGetDeviceIDs()迭代处理这个平台列表,获取各个平台支持的一组CPU设备类型,如果至少找到一个设备,这个循环就终止。如果没有找到任何CPU设备,程序会直接退出;否则会用找到的设备列表创建一个上下文,然后从磁盘加载内核源代码并编译,创建一个内核对象。再创建输入/输出缓冲区,最后设置内核参数并执行这个内核。程序最后读取输出的信号,并把结果输出到stdout

Convolution.cpp

//
// Book:      OpenCL(R) Programming Guide
// Authors:   Aaftab Munshi, Benedict Gaster, Timothy Mattson, James Fung, Dan Ginsburg
// ISBN-10:   0-321-74964-2
// ISBN-13:   978-0-321-74964-2
// Publisher: Addison-Wesley Professional
// URLs:      http://safari.informit.com/9780132488006/
//            http://www.openclprogrammingguide.com
//


// Convolution.cpp
//
//    This is a simple example that demonstrates OpenCL platform, device, and context
//    use.

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>

#ifdef __APPLE__
#include <OpenCL/cl.h>
#else
#include <CL/cl.h>
#endif

#if !defined(CL_CALLBACK)
#define CL_CALLBACK
#endif

#pragma warning( disable : 4996 )
// Constants
const unsigned int inputSignalWidth = 8;
const unsigned int inputSignalHeight = 8;

cl_uint inputSignal[inputSignalWidth][inputSignalHeight] =
{
	{3, 1, 1, 4, 8, 2, 1, 3},
	{4, 2, 1, 1, 2, 1, 2, 3},
	{4, 4, 4, 4, 3, 2, 2, 2},
	{9, 8, 3, 8, 9, 0, 0, 0},
	{9, 3, 3, 9, 0, 0, 0, 0},
	{0, 9, 0, 8, 0, 0, 0, 0},
	{3, 0, 8, 8, 9, 4, 4, 4},
	{5, 9, 8, 1, 8, 1, 1, 1}
};

const unsigned int outputSignalWidth = 6;
const unsigned int outputSignalHeight = 6;

cl_uint outputSignal[outputSignalWidth][outputSignalHeight];

const unsigned int maskWidth = 3;
const unsigned int maskHeight = 3;

cl_uint mask[maskWidth][maskHeight] =
{
	{1, 1, 1}, {1, 0, 1}, {1, 1, 1},
};

///
// Function to check and handle OpenCL errors
inline void
checkErr(cl_int err, const char* name)
{
	if (err != CL_SUCCESS) {
		std::cerr << "ERROR: " << name << " (" << err << ")" << std::endl;
		exit(EXIT_FAILURE);
	}
}

void CL_CALLBACK contextCallback(
	const char* errInfo,
	const void* private_info,
	size_t cb,
	void* user_data)
{
	std::cout << "Error occured during context use: " << errInfo << std::endl;
	// should really perform any clearup and so on at this point
	// but for simplicitly just exit.
	exit(1);
}

///
//	main() for Convoloution example
//
int main(int argc, char** argv)
{
	cl_int errNum;
	cl_uint numPlatforms;
	cl_uint numDevices;
	cl_platform_id* platformIDs;
	cl_device_id* deviceIDs;
	cl_context context = NULL;
	cl_command_queue queue;
	cl_program program;
	cl_kernel kernel;
	cl_mem inputSignalBuffer;
	cl_mem outputSignalBuffer;
	cl_mem maskBuffer;

	// First, select an OpenCL platform to run on.  
	errNum = clGetPlatformIDs(0, NULL, &numPlatforms);
	checkErr(
		(errNum != CL_SUCCESS) ? errNum : (numPlatforms <= 0 ? -1 : CL_SUCCESS),
		"clGetPlatformIDs");

	platformIDs = (cl_platform_id*)alloca(
		sizeof(cl_platform_id) * numPlatforms);

	errNum = clGetPlatformIDs(numPlatforms, platformIDs, NULL);
	checkErr(
		(errNum != CL_SUCCESS) ? errNum : (numPlatforms <= 0 ? -1 : CL_SUCCESS),
		"clGetPlatformIDs");

	// Iterate through the list of platforms until we find one that supports
	// a CPU device, otherwise fail with an error.
	deviceIDs = NULL;
	cl_uint i;
	for (i = 0; i < numPlatforms; i++)
	{
		errNum = clGetDeviceIDs(
			platformIDs[i],
			CL_DEVICE_TYPE_CPU,
			0,
			NULL,
			&numDevices);
		if (errNum != CL_SUCCESS && errNum != CL_DEVICE_NOT_FOUND)
		{
			checkErr(errNum, "clGetDeviceIDs");
		}
		else if (numDevices > 0)
		{
			deviceIDs = (cl_device_id*)alloca(sizeof(cl_device_id) * numDevices);
			errNum = clGetDeviceIDs(
				platformIDs[i],
				CL_DEVICE_TYPE_CPU,
				numDevices,
				&deviceIDs[0],
				NULL);
			checkErr(errNum, "clGetDeviceIDs");
			break;
		}
	}

	// Check to see if we found at least one CPU device, otherwise return
	if (deviceIDs == NULL) {
		std::cout << "No CPU device found" << std::endl;
		exit(-1);
	}

	// Next, create an OpenCL context on the selected platform.  
	cl_context_properties contextProperties[] =
	{
		CL_CONTEXT_PLATFORM,
		(cl_context_properties)platformIDs[i],
		0
	};
	context = clCreateContext(
		contextProperties,
		numDevices,
		deviceIDs,
		&contextCallback,
		NULL,
		&errNum);
	checkErr(errNum, "clCreateContext");

	std::ifstream srcFile("Convolution.cl");
	checkErr(srcFile.is_open() ? CL_SUCCESS : -1, "reading Convolution.cl");

	std::string srcProg(
		std::istreambuf_iterator<char>(srcFile),
		(std::istreambuf_iterator<char>()));

	const char* src = srcProg.c_str();
	size_t length = srcProg.length();

	// Create program from source
	program = clCreateProgramWithSource(
		context,
		1,
		&src,
		&length,
		&errNum);
	checkErr(errNum, "clCreateProgramWithSource");

	// Build program
	errNum = clBuildProgram(
		program,
		numDevices,
		deviceIDs,
		NULL,
		NULL,
		NULL);
	if (errNum != CL_SUCCESS)
	{
		// Determine the reason for the error
		char buildLog[16384];
		clGetProgramBuildInfo(
			program,
			deviceIDs[0],
			CL_PROGRAM_BUILD_LOG,
			sizeof(buildLog),
			buildLog,
			NULL);

		std::cerr << "Error in kernel: " << std::endl;
		std::cerr << buildLog;
		checkErr(errNum, "clBuildProgram");
	}

	// Create kernel object
	kernel = clCreateKernel(
		program,
		"convolve",
		&errNum);
	checkErr(errNum, "clCreateKernel");

	// Now allocate buffers
	inputSignalBuffer = clCreateBuffer(
		context,
		CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
		sizeof(cl_uint) * inputSignalHeight * inputSignalWidth,
		static_cast<void*>(inputSignal),
		&errNum);
	checkErr(errNum, "clCreateBuffer(inputSignal)");

	maskBuffer = clCreateBuffer(
		context,
		CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
		sizeof(cl_uint) * maskHeight * maskWidth,
		static_cast<void*>(mask),
		&errNum);
	checkErr(errNum, "clCreateBuffer(mask)");

	outputSignalBuffer = clCreateBuffer(
		context,
		CL_MEM_WRITE_ONLY,
		sizeof(cl_uint) * outputSignalHeight * outputSignalWidth,
		NULL,
		&errNum);
	checkErr(errNum, "clCreateBuffer(outputSignal)");

	// Pick the first device and create command queue.
	queue = clCreateCommandQueue(
		context,
		deviceIDs[0],
		0,
		&errNum);
	checkErr(errNum, "clCreateCommandQueue");

	errNum = clSetKernelArg(kernel, 0, sizeof(cl_mem), &inputSignalBuffer);
	errNum |= clSetKernelArg(kernel, 1, sizeof(cl_mem), &maskBuffer);
	errNum |= clSetKernelArg(kernel, 2, sizeof(cl_mem), &outputSignalBuffer);
	errNum |= clSetKernelArg(kernel, 3, sizeof(cl_uint), &inputSignalWidth);
	errNum |= clSetKernelArg(kernel, 4, sizeof(cl_uint), &maskWidth);
	checkErr(errNum, "clSetKernelArg");

	const size_t globalWorkSize[1] = { outputSignalWidth * outputSignalHeight };
	const size_t localWorkSize[1] = { 1 };

	// Queue the kernel up for execution across the array
	errNum = clEnqueueNDRangeKernel(
		queue,
		kernel,
		1,
		NULL,
		globalWorkSize,
		localWorkSize,
		0,
		NULL,
		NULL);
	checkErr(errNum, "clEnqueueNDRangeKernel");

	errNum = clEnqueueReadBuffer(
		queue,
		outputSignalBuffer,
		CL_TRUE,
		0,
		sizeof(cl_uint) * outputSignalHeight * outputSignalHeight,
		outputSignal,
		0,
		NULL,
		NULL);
	checkErr(errNum, "clEnqueueReadBuffer");

	// Output the result buffer
	for (int y = 0; y < outputSignalHeight; y++)
	{
		for (int x = 0; x < outputSignalWidth; x++)
		{
			std::cout << outputSignal[x][y] << " ";
		}
		std::cout << std::endl;
	}

	std::cout << std::endl << "Executed program succesfully." << std::endl;

	return 0;
}

在这里插入图片描述

卷积

卷积运算是指从图像的左上角开始,开一个与模板同样大小的活动窗口,窗口图像与模板像元对应起来相乘再相加,并用计算结果代替窗口中心的像元亮度值。然后,活动窗口向右移动一列,并作同样的运算。以此类推,从左到右、从上到下,即可得到一幅新图像。
在这里插入图片描述
如上图,卷积操作其实就是每次取一个特定大小的矩阵F(蓝色矩阵中的阴影部分),然后将其对输入X(图中蓝色矩阵)依次扫描并进行内积的运算过程。可以看到,阴影部分每移动一个位置就会计算得到一个卷积值(绿色矩阵中的阴影部分),当F扫描完成后就得到了整个卷积后的结果Y(绿色矩阵)。

同时,我们将这个特定大小的矩阵F称为卷积核,即convolutional kernel或kernel或filter或detector,它可以是一个也可以是多个;将卷积后的结果Y称为特征图,即feature map,并且每一个卷积核卷积后都会得到一个对应的特征图;最后,对于输入X)的形状,都会用三个维度来进行表示,即宽(width),高(high)和通道(channel)。例如图中输入X的形状为[7,7,1]。

多卷积核

卷积核的个数还可以是多个,那我们为什么需要多个卷积核进行卷积呢?

对于一个卷积核,可以认为其具有识别某一类元素(特征)的能力;而对于一些复杂的数据来说,仅仅只是通过一类特征来进行辨识往往是不够的。因此,通常来说我们都会通过多个不同的卷积核来对输入进行特征提取得到多个特征图,然再输入到后续的网络中。

多卷积核视频

如上图所示,对于同一个输入,通过两个不同的卷积核对其进行卷积特征提取,最后便能得到两个不同的特征图。从图2右边的特征图可以发现,上面的特征图在锐利度方面明显会强于下面的特征图。当然,这也是使用多卷积核进行卷积的意义,探测到多种特征属性以有利于后续的下游任务。

偏置项

偏置项(bias)是一个常数,它与卷积核一起作用于输入数据,用于调整输出结果的偏移。具体来说,偏置项可以看作是一个与卷积核大小相同、但只有一个深度的数组,其中的每个元素都加到卷积的输出中。

卷积层的中偏置项可以帮助模型学习数据的偏移量和偏差,从而提高模型的准确性和稳定性。偏置项的调整可以通过反向传播算法自动完成,使得模型能够快速适应不同的数据。

在实际应用中,偏置项的值通常设置为0或一个小的常数,以免梯度爆炸或梯度消失等问题。同时,由于偏置项与卷积核的大小相同,因此在训练过程中需要对其进行更新和调整。

单通道单卷积核

如下图所示,现在有一张形状为[5,5,1]的灰度图,我们需要用图3右边的卷积核对其进行卷积处理,同时再考虑到偏置的作用。那么其计算过程是怎么样的呢?
在这里插入图片描述
如下图所示,右边为卷积后的特征图(feature map),左边为卷积核对输入图片左上放进行卷积时的示意图。因此,对于这个部分的计算过程有:
在这里插入图片描述
同理,对于最右下角部分卷积计算过程有:
在这里插入图片描述
因此,对于最后卷积的结果,我们得到的将是一个如图5右边所示形状为[3,3,1]的特征图。到此我们就把单通道单卷积的计算过程介绍完了。下面我们再来看单通道多卷积核的例子。

单通道多卷积核

如下图所示,左边依旧为输入矩阵,我们现在要用右边所示的两个卷积核对其进行卷积处理。
在这里插入图片描述
分别对两个卷积核进行计算,如下
在这里插入图片描述
在这里插入图片描述
最后我们便能得到如图8右边所示的,形状为[3,3,2]的卷积特征图,其中2表示两个特征通道。
在这里插入图片描述

多通道单卷积核

对于多通道的卷积过程,总体上还是还是同之前的一样,都是每次选取特定位置上的神经元进行卷积,然后依次移动直到卷积结束。下面我们先来看看多通道单卷积核的计算过程。
在这里插入图片描述
如上图所示,左边为包含有三个通道的输入,右边为一个卷积核和一个偏置。注意,强调一下右边的仅仅只是一个卷积核,不是三个。笔者看到不少人在这个地方都会搞错。因为输入是三个通道,所以在进行卷积的时候,对应的每一个卷积核都必须要有三个通道才能进行卷积。下面我们就来看看具体的计算过程。
在这里插入图片描述
如上图所示,右边为卷积后的特征图(feature map),左边为一个三通道的卷积核对输入图片左上放进行卷积时的示意图。因此,对于这个部分的计算过程有:
在这里插入图片描述
同理,对于其它部分的卷积计算过程也类似于上述计算步骤。由此我们便能得到如上图右边所示卷积后的形状为[3,3,1]的特征图。

多通道多卷积核

我们再来看看多通道多卷积核的计算过程。
在这里插入图片描述
如上图所示,左边依旧为输入矩阵,我们现在要用右边所示的两个卷积核对其进行卷积处理。同时可以看到,第一个卷积核就是图9中所示的卷积核,其结果如图10所示。对于第二个卷积核,其计算过程也和式子(3)类似,都是将每个通道上的卷积结果进行相加,最后再加上偏置。因此,最后我们便能得到如下图右边所示的,形状为[3,3,2]的卷积特征图,其中2表示两个特征通道。
在这里插入图片描述
在这里插入图片描述
同时,从上面单通道卷积核多通道卷积的计算过程可以发现:
(1)原始输入有多少个通道,其对应的一个卷积核就必须要有多少个通道,这样才能与输入进行匹配,也才能完成卷积操作。换句话说,如果输入数据的形状为[n,n,c],那么对应每个卷积核的通道数也必须为c。
(2)用k个卷积核对输入进行卷积处理,那么最后得到的特征图一定就会包含有k个通道。例如,输入为[n,n,c],且用k个卷积核对其进行卷积,则卷积核的形状必定为[w1,w2,c,k],最终得到的特征图形状必定为[h1,h2,k];其中w1,w2为卷积核的宽度,h1,h2为卷积后特征图的宽度。

深度卷积

深度卷积就是卷积之后再卷积,然后再卷积。卷积的次数可以是几次,也可以是几十次、甚至可以是几百次。在全连接网络中我们可以通过更深的隐藏层来获取到更高级和更抽象的特征,以此来提高下游任务的精度。因此,采用深度卷积也是处于同样的目的。

卷积操作可以看作是对上一次输入的特征提取,即用来抓取输入中是否包含有某一类的特征。但是,通常情况下,输入的图像数据都是由一系列特征横向和纵向组合叠加起来的。因此,对于同一层次(横向)的特征我们需要通过多个卷积核对输入进行特征提取;而对于不同层次(纵向)的特征我们需要通过卷积的叠加来进行特征提取。
在这里插入图片描述
如上图,对于输入的一张图片,我们可以通过取多次叠加卷积后的结果来进行物体的分类任务。从图中可以发现,对于一开始的几次卷积,我们还能看到一些汽车的轮廓;但是在后续的多次叠加卷积处理后,我们人眼也就再也看不所谓汽车的影子了。但是,这些更高级的、抽象的特征却真实的能够提高模型最终的任务精度。因此,在一定的条件下,你甚至可以认为卷积的次数越多越好。

在相邻空间位置上具有依赖关系的数据均可以通过卷积操作来进行特征提取。

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

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

相关文章

算法训练Day53:​ 1143.最长公共子序列 1035.不相交的线 53.最大子序和 动态规划

文章目录 最长公共子序列题解 不相交的线题解 最大子数组和题解 最长公共子序列 CategoryDifficultyLikesDislikesContestSlugProblemIndexScorealgorithmsMedium (64.94%)13110--0 Tags Companies 给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长 公共子…

浏览csdn博客自动隐藏侧边栏并只看目录

背景 CSDN 总算做了点好事&#xff0c;能够隐藏大部分无关信息&#xff0c;只看博客内容本身。具体如图&#xff0c;还在测试版 以我的一篇博客为例&#xff0c;原始界面&#xff0c;花里胡哨一堆 点击隐藏侧栏后的清爽版 点击只看目录后的清爽版 前提提要 安装油猴脚本&…

使用VSCode创建Vue项目

Vue介绍 Vue.js是一个渐进式JavaScript框架&#xff0c;用于构建用户界面。它可以与其他库或现有项目集成&#xff0c;也可以作为单个组件使用。Vue.js的目标是提供一种简单、快速和灵活的方式来开发交互式Web应用程序。 Vue.js的核心特性包括&#xff1a; 响应式数据绑定&am…

http协议格式

HyperText Transfer Tansfer Protocol 超文本传输协议&#xff0c;是一种基于TCP的应用层协议&#xff0c;也是目前为止最为流行的应用层协议之一&#xff0c;可以说HTTP协议是万维网的基石。历经了0.9、HTTP/1.0、HTTP/1.1、HTTP/2几个版本(关于HTTP协议的历史&#xff0c;这里…

unity-物体rotation翻转180度后,OnPointerDown失效的问题

问题&#xff1a;今天碰到一个问题&#xff0c;就是把物体A进行水平翻转后&#xff0c;如下图&#xff0c;OnPointerDown 就失效了 》解决方案1&#xff08;使用Scale X来替代Rotation Y&#xff09;&#xff1a; 使用Scale改为-1来翻转&#xff0c;这样 OnPointerDown 就正常…

双向链表刷题总结

剑指 Offer 36. 二叉搜索树与双向链表 输入一棵二叉搜索树&#xff0c;将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点&#xff0c;只能调整树中节点指针的指向。 为了让您更好地理解问题&#xff0c;以下面的二叉搜索树为例&#xff1a; 我们希望将这…

分布式系统中的那些一致性(CAP、BASE、2PC、3PC、Paxos、ZAB、Raft)

本文介绍 CAP、BASE理论的正确理解、Paxos 算法如何保证一致性及死循环问题、ZAB 协议中原子广播及崩溃恢复以及 Raft 算法的动态演示。 下面还有投票&#xff0c;一起参与进来吧&#x1f44d; 文章目录 前言CAP理论理解误导正确的理解CAP理论的应用 BASE理论Paxos算法如何保证…

ASEMI代理LT6230CS6-10#TRPBF原装ADI车规级LT6230CS6-10#TRPBF

编辑&#xff1a;ll ASEMI代理LT6230CS6-10#TRPBF原装ADI车规级LT6230CS6-10#TRPBF 型号&#xff1a;LT6230CS6-10#TRPBF 品牌&#xff1a;ADI /亚德诺 封装&#xff1a;SOT-6 批号&#xff1a;2023 安装类型&#xff1a;表面贴装型 引脚数量&#xff1a;6 工作温度:-4…

sentinel介绍

介绍 官网地址 Sentinel 和 Hystrix 的原则是一致的: 当调用链路中某个资源出现不稳定&#xff0c;例如&#xff0c;表现为 timeout&#xff0c;异常比例升高的时候&#xff0c;则对这个资源的调用进行限制&#xff0c;并让请求快速失败&#xff0c;避免影响到其它的资源&…

阿里云争食币圈

阿里云的触手正在向币圈延伸。几天前&#xff0c;阿里云与Avalanche区块链和MUA DAO联合推出Cloudverse&#xff0c;为想要在链上部署元宇宙的企业提供一站式解决方案。 Avalanche是典型的币圈项目&#xff0c;链上的一切价值流转都以加密货币结算。此次合作释放出阿里云在Web…

apt 与 dpkg 命令详解

一. apt & dpkg 异同点 1. apt 与 dpkg 均为 ubuntu 下面的包管理工具。 2. dpkg 仅用于安装本地的软件包&#xff0c;安装时不会安装依赖包&#xff0c;不解决依赖问题。 sudo dpkg -i <package_name>.deb 3. apt 默认会从远程仓库搜索包的名字&#xff0c;下载并安…

多元线性回归——自相关(二)

自相关问题 文章目录 自相关问题(R)[toc]1 什么是自相关2 自相关产生的原因3 自相关的后果4 自相关检验5 自相关补救6 R语言操作 1 什么是自相关 经典普通最小二乘法估计的假设之一是扰动项不存在自相关&#xff0c;即对于 ∀ i ≠ j \forall i\ne j ∀ij,都有 C o v ( μ …

Kali-linux测试网络范围

测试网络范围内的IP地址或域名也是渗透测试的一个重要部分。通过测试网络范围内的IP地址或域名&#xff0c;确定是否有人入侵自己的网络中并损害系统。不少单位选择仅对局部IP基础架构进行渗透测试&#xff0c;但从现在的安全形势来看&#xff0c;只有对整个IT基础架构进行测试…

5G+工业物联网——解密“智能矿山”背后的黑科技

当前&#xff0c;以5G为代表的新一代信息技术正在飞速发展并加快应用&#xff0c;以工业物联网为代表的新型基础设施建设则推动着传统制造业数字化转型发展。依托我国5G全球最大的规模网络以及国家出台的一系列政策&#xff0c;“5G工业物联网”变成应用创新最活跃的行业之一。…

【Linux】认识高级IO 5种IO模型

文章目录 高级IOIO的基本概念什么是IOOS如何得知外设当中有数据可读取OS如何处理从网卡中读取到的数据包IO的步骤 五种IO模型钓鱼的例子对应的模型如何区分同步IO和异步IO 阻塞IO非阻塞IO信号驱动IOIO多路转接异步IO 高级IO的概念同步通信 VS 异步通信阻塞 VS 非阻塞 阻塞IO非阻…

基于springboot的家政服务平台的设计与实现

背景 现代社会&#xff0c;由于经济不断发展&#xff0c;家政服务的数量也在不断的增加&#xff0c;随着家政服务的数量增多&#xff0c;人们对家政服务信息的需求也越来越高。 以往的家政服务管理平台的管理&#xff0c;一般都是纸质文件来管理家政服务信息&#xff0c;传统…

Python基本数据类型 — 列表

一、列表基本操作 1、创建列表 &#xff08;1&#xff09;使用 [] 创建列表 创建一个空列表&#xff0c;可以使用以下代码&#xff1a; my_list []创建一个包含元素的列表&#xff0c;可以在方括号中使用逗号分隔值&#xff0c;如下所示&#xff1a; my_list [1, 2, 3, …

Qt编写视频监控系统72-通过onvif增删改查OSD

一、前言 之前监控系统中原创的onvif协议解析机制&#xff0c;已经能够满足绝大部分用户的需要&#xff0c;比如搜索设备、获取视频流地址并播放、云台控制、预置位管理、图片亮度色彩饱和度等参数设置等&#xff0c;近期又多了一个需求&#xff0c;那就是通过onvif国际标准协…

平台使用篇 | RflySim平台飞控固件上传教程

导读 本教程共介绍了4种飞控固件的上传方式&#xff0c;重点介绍了Simulink模型生成自定义固件上传的两种方法&#xff0c;其中固件上传步骤主要分为三步&#xff1a;下载源码、编译生成固件及最后上传固件。 01 PX4官方上传方式 详见PX4官方固件烧录教程&#xff1a;https:…

基于spring boot + maven + opencv 实现的图像深度学习

写在前面的话 这是一个基于spring boot maven opencv 实现的Demo教程项目贯穿样本处理、模型训练、图像处理、对象检测、对象识别等技术点以学习交流为目的&#xff0c;代码注释超多&#xff0c;文档也在逐步完善java语言的深度学习项目&#xff0c;在整个开源社区来说都相对…