基于WebGL实现矩阵计算

Posted 神机喵算

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于WebGL实现矩阵计算相关的知识,希望对你有一定的参考价值。

节选自《深度学习TensorFlow.js:浏览器实战篇》第五章,已获授权。

基于纹理和着色器的矩阵计算

学完WebGL的GPGPU之后,我们回到深度学习,构建一个简单的WebGL线性代数库WGLMatrix。然后我们使用WGLMatrix实现一个简单的神经网络(不是卷积神经网络)从MNIST数据集中学习识别手写数字。线性代数库的第一个测试版在代码仓库chapter5/4_WGLMatrix

标准的矩阵加法

GLSL中的内建矩阵类型的最大维度不多于四维,这对深度学习是不够的。所以用纹理存储矩阵,我们需要开发自定义的着色器程序进行矩阵操作。每个纹理元素时一个矩阵项,纹理的分辨率和矩阵的维度一样大小。

当创建一个矩阵,如果用四个javascript初始化的数组,RGBA通道将用这些来填充。如果只有一个数组,那它的值将会重复四次以填充颜色通道。每个常用的矩阵计算操作对于RGBA通道来说,是分开处理的。就好像用四个不同的矩阵并行的处理四次。

矩阵加法的片段着色器程序像其它对位的操作一样,不依赖矩阵的大小,下面是对应的GLSL代码:

void main(void){ vec2 uv=gl_FragCoord.xy/resolution; vec4 matAValue=texture2D(samplerTexture0, uv); vec4 matBValue=texture2D(samplerTexture1, uv); gl_FragColor=matAValue+matBValue;
标准的矩阵乘法:

和矩阵加法的着色器程序不同的是,矩阵乘法的着色器用for循环将左矩阵第一行乘以右矩阵第一列。在WebGL1中for条件不能使用非常数值。该限制包括GLSL的 uniform类型值或者上一步计算的值。所以,我们需要为每个矩阵乘法编译一个着色器程序。例如,下面的着色器程序可以进行所有的(n, 10)矩阵和(10,n)矩阵相乘:

//vector between 2 consecutive texels of first factor:const vec2 DU=vec2(1./10., 0.);//vector between 2 consecutive texels of second factor: const vec2 DV=vec2(0., 1./10.);
void main(void){vec2 uv=gl_FragCoord.xy/resolution; vec2 uvu=uv*vec2(1.,0.);vec2 uvv=uv*vec2(0.,1.);vec4 result=vec4(0.,0.,0.,0.);for (float i=0.0; i<10.0; i+=1.0){ result+=texture2D(samplerTexture0, uvv+(i+0.5)*DU) *texture2D(samplerTexture1, uvu+(i+0.5)*DV);} gl_FragColor=result; }

上面的代码中,对于每个i增加0.5是为了获取像素的中间值,反之,对于某些矩阵维度可能会出现舍入错误。在WebGL2中,for循环条件实现了非常数条件。但是,这对于只支持WebGL2的商业应用是不能接受的(在2018年3月,支持率是41%,数据来源于webglstats.com)。所以我们构建的WebGL1着色器程序也支持WebGL2.

我们经常会同时做矩阵相乘和矩阵相加。例如,我们将权重矩阵和神经网络layer输入矩阵X相乘,然后增加偏置矩阵B求和得到输入矩阵Z:Z = WX + B。我们应该编写一个自定义的着色器程序实现该功能,称为FMA(Fused Multiply–Accumulate)。它将会节省一些渲染到纹理的过程。

矩阵库WGLMatrix管理矩阵乘法和FMA着色器程序的一个字典。如果我们需要处理两个矩阵,其中一个矩阵的维度包含在字典,则我们只需要用字典中的着色器程序。否则,需要重新编写新的维度着色器程序,并增加到字典。

激活函数

着色器程序致力于激活函数的应用。我们需要注意浮点型特殊值,特别地,激活函数涉及到指数函数或者对数函数。下面的着色器程序应用于sigmoid激活函数:

const vec4 ONE=vec4(1.,1.,1.,1.); void main(void) {vec2 uv=gl_FragCoord.xy/resolution; vec4 x=texture2D(samplerTexture0, uv); vec4 y;y=1./(ONE+exp(-x)); gl_FragColor=y; }

因为许多自定义的激活函数都被应用于矩阵,所以我们在WGLMatrix库设置公共方法来编写自定义着色器程序。

运用WGLMatrix库

每个矩阵在使用之前都会初始化。用扁平的值分别去初始化矩阵m、v、n:

// encoding 3*3 matrix | 0 1 2 |:// | 3 4 5 |// | 6 7 8 | varM=newWGLMatrix.Matrix(3,3,[0,1,2, 3,4,5, 6,7,8]); //V is a column matrix, which is a vector:var V=new WGLMatrix.Matrix(3,1,[1,2,3]); var W=new WGLMatrix.MatrixZero(3,1);

从数学上讲,一个向量和一个矩阵没有什么区别,维度为1。初始化之后我们可以进行矩阵操作。为了对矩阵A执行OPERATION操作,执行如下代码:

A.OPERATION(arguments..., R)

R是计算结果矩阵。我们不能将操作结果存储在所涉及的任何矩阵中,因为不可能同时读取一个纹理并渲染到该纹理。所以操作经常返回结果矩阵R。

例如,为了进行操作W=M*V,执行下面的代码:

M.multiply(V, W);

上面的代码返回矩阵W。我们可以声明一个自定义的对位操作,执行如下代码:

WGLMatrix.addFunction('y=cos(x);', 'COS');

上面代码中第一个参数是函数的GLSL代码,使用预定义的向量vec4 x和vec4 y。第二个参数是函数的标记。然后将其应用于矩阵M的所有元素:M.apply('COS', R)。其中R是返回的结果矩阵。


若发现以上文章有任何不妥,请联系我。




以上是关于基于WebGL实现矩阵计算的主要内容,如果未能解决你的问题,请参考以下文章

WebGL学习系列-基础矩阵变换

Phong 和 Gouraud 着色 WebGL

WebGL简易教程:包围球与投影

计算机图形学基于WebGL的纹理贴图

shader编程-二维空间中使用矩阵实现物体的旋转缩放平移变换(WebGL-Shader开发基础03)

shader编程-二维空间中使用矩阵实现物体的旋转缩放平移变换(WebGL-Shader开发基础03)