这是我参与8月更文挑战的第18天,活动详情查看:8月更文挑战
Σ( ° △ °|||)︴本篇文章要讲如何对公式进行向量化来简化计算,可能会穿插一切废话,都用引用符号标记出来了,可以不看。
如何抛开人的主观逻辑思考?怎么用更适合计算机的角度分析问题?
可能很多人不不理解我这个问题,那我举个栗子。因为我本科是学生物的,本科时期计算机算是辅修。起初我就毫无这种简化问题的思维。
比如一个简单的十进制转二进制。在我眼里,逻辑上可以算作有两种方法:
方法一:看看这个数是2的几次方的和
819=1×29+1×28+0×27+0×26+1×25+1×24+0×23+0×22+1×21+1×20
方法二:除留取余法
819÷2=409...1
409÷2=204...1
204÷2=102...0
102÷2=51.....0
51÷2=25.......1
25÷2=12.......1
12÷2=6.........0
6÷2=3...........0
3÷2=1...........1
1÷2=0...........1
当时在我眼里,我觉得怎么看都是方法一更简单,看看是二的几次方就行了。直到我一次刷题遇到进制转换突然蒙了……咦?这怎么办唉。除2的几次方这个逻辑怎么写唉。难道搞两个数组,一个存2的n次方,一个存除数结果吗?
然后我去求助,计算机的学长说你就学了这一种方法?我说不啊我还会别的。他说那你用另个方法写一下就好了。然后我发现用除留取余法明显更简单。
这就是我说的更适合计算机分析的角度处理问题,也许我主管认为某种方法简单,但是对于计算机来讲可能另一种处理方法更便于实现,所以对于一些问题,我们要转换思路,找到更适合计算机处理的方法来进行。
所以对于深度学习,“复杂的公式”如何简化计算?
举个简单栗子:
hθ(x)=∑j=0nθjxj
如果让你自己手算这个,你肯定会想,不就是个简单的一次函数吗,挨个带进去算就行了。但是如果“带进去算”这个方法让计算机来实现就会变成这样:
声明两个向量,遍历。
Unvectorized implementation(没向量化)
C++:
double prediction = 0.0;
for(int j = 0; j<=n; j++)
{
prediction += theta[j]*x[j];
}
octave:
prediction =0.0;
for j = 1:n+l
prediction = prediction + theta(j) * x(j)
end
上边提到了“没向量化”。什么是向量化呢? 就是将你认知上的公式转化成矩阵和向量的乘法。这样计算机处理起来就会更迅速,也就是我前边提到的“更适合计算机的角度去处理问题”。
hθ(x)=∑j=0nθjxj 的向量化可以将其看做是θTx而进行计算。
这样就转化为两个向量θ=⎣⎡θ0θ1θ2...⎦⎤x=⎣⎡x0x1x2...⎦⎤的乘积了。
Vectorized implementation(向量化之后)
octave:
prediction= theta' * x;
C++:
double prediction = theta.transpose()*x;
向量化之后为什么比你自己用数组计算迅速呢?因为向量化之后可以更便捷的用到一些语言内置的库。比如上边octave中的theta'
英文单引号 ' 表示求矩阵或向量的转置(忘记的回去看octave语法)。C++中theta.transpose()
也是用到了线性代数库的转置函数。
作为一个学生物的,我是用C++作为入门语言的,刚开始学数据结构和算法的时候,比如排序,最然我了解各种排序及其效率。但是如果问我给出一组什么特征的数据用哪种排序算法效率高,我才会考虑用哪个更好,否则刷题时候我一般都是直接冒泡。有次学长跟我说:你说C++库函数用的是哪个排序。我不知道。然后他说:你可以闲着没事去看看库函数源代码,比如C++的排序,不是单一某个排序而是排序的组合体,都是各种大佬研究优化出来的算法。
使用高级语言的库,更便捷了我们的操作。这些库函数都是各种计算机的大佬创造优化出来的,比我们自己写便捷千百倍。
用内置算法的好处:
- 速度更快
- 用更少代码实现
- 相比于你自己写的更不易出错
- 更好地配合硬件系统
说了这么多,再来个栗子:
θj:=θj−αm1∑i=1m(hθ(x(i))−y(i))xj(i) for all j
这个公式很熟悉,是梯度下降的公式。
在这个公式中x是数据矩阵,y是列向量。
对于多元线性回归:
θ0:=θ0−αm1∑i=1m(hθ(x(i))−y(i))x0(i)θ1:=θ1−αm1∑i=1m(hθ(x(i))−y(i))x1(i)θ2:=θ2−αm1∑i=1m(hθ(x(i))−y(i))x2(i)...
先简化一下梯度下降公式使其更容易编写代码:
θ:=θ−αδ
δ=m1i=1∑m(hθ(x(i))−y(i))x(i)
其中δ是个列向量:δ=⎣⎡δ0δ1δ2...⎦⎤
而对于x来说,x(i)=⎣⎡x0(i)x1(i)x2(i)...⎦⎤
δ=数×(...)×x(i)
由此可是一定是 δn×1=数×(...)×x1×n(i) 即xj(i)进行转置。
这个逻辑如果不向量化的话可能需要写好多循环才能完成。
我随手用c++写了一下,不保证对啊,你们大致看一下就行了,我也没运行这段代码。
for(int j=0;j<n;j++)
{
for(int i=0;i<len;i++)
{
hx[i] += theta[i][j]*x[i][j];
}
for(int i=0;i<len;i++)
{
sum += (h[i] - y[i])*x[i][j];
}
theta[j] = theta[j] - alpha * (1/m) * sum;
}
但是如果你向量化以后就可以写为:
% 假设现在是二元的
hx = X * theta;
theta(1) = theta(1) - alpha * (1/m) * sum((hx-y)*X(:,1:1))
theta(2) = theta(2) - alpha * (1/m) * sum((hx-y)*X(:,2:2))
%注意octave和C等其他语言不同,下标从1开始
% 如果是n元的
hx = X * theta;
for i = 1:n
theta(i) = theta(i) - alpha * (1/m) * sum((hx-y)*X(:,i:i))
%注意octave和C等其他语言不同,下标从1开始
endfor
向量化之后怎么都好处理,但是如果不经过向量化,那你可能就要嵌套好多for循环了。所以要善用向量化减少工作量。