本想边写边修改,不想简码站不可以修改已发布的内容,所以浪费了沙发。。。
因为是学习记录帖,那么可能会忽略各位看官的感受,这个过程可能不会太连贯,我也是脚踩西瓜皮,滑到哪儿算哪儿,这里抱歉了。如果有愿意看的人,给点意见,给点指导,本人表示万分感谢。帖子里可能会转述他人的成果,包括代码、图片等,这里一并对这些有分享精神的坛友表示感谢。
之前,花过不少精力琢磨过验证码的识别,对于比较规则的验证码,都能搞定。只是这种方法,只能针对某站的某型的验证码有效,那么有没有比较通用的方法呢?曾经在某群看到过一个验证码识别的模块,对于干扰线、变形、重叠的验证码,识别率也很高,是bp神经网络一个应用。开口1万买源代码,对方不同意作罢。为什么我愿意花这个钱,主要是考虑本人接单的类型,大部分都有识别验证码的要求。对接打码平台,也能解决问题,但是打码平台都是向服务器请求,这个效率比较低,识别率也并不高。买这个源代码,是想把自己的这个老大难问题解决掉。虽然网络神经也不能解决所有类型的验证码识别,能解决掉大多数类型也是非常值得一试的。把这套方法搞清楚之后,可以应用到其他问题的解决。
这两天搜了一些资料,大多数都是晦涩难懂的理论、数学公式、专业名词,看起来头疼。那么需要把相关的内容系统的恶补一下吗?我担心自己很难坚持下来,很容易半途而废。那么,就按照自己的方法来吧,从最简单的开始,碰到不懂的就去查资料,有些孤立的概念或者公式直接强记,先把骨架搭起来,先把这个理论的脉络搞清楚,皮肉慢慢充实。具体到如何搭建一个神经网络,网络上相关内容比较少,主要是些英文资料,本人英文稀烂。这里不转述别人的文章,主要结合自己的理解用aar代码来实现。
这里借用一下别人的图片:
在这个问题里,之前有4组数据输入,得到4个数据的输出,问第5次会输出啥?
常规来说,我们肉眼来分析这个规律,马上就可以得到输出的都是输入的第一个字符。
那么就像验证妈的识别,对于特定的验证码,识别特征都是人工来确定,但是如何准确的提取识别特征是个问题,提取到了特征又如何用代码表达出来是个更大的问题。神经网络的牛逼之处,就在于通过学习训练来获取识别特征,所谓卷积神经网络的卷积核是通过训练来收敛到某一组值,即特征,这一点最难理解……看了三天资料,这个问题理解了对我之后的学习有点睛之效。那么也解决了之前的疑问,神经网络那么高大上,他比别的方法究竟牛逼在何处。
在上述的问题中,这个特征就是输出的都是输入的第一个字符,那么我们很容易判断第5次会输出1。这个简单的例子,只是为了更好的说明神经网络是如何工作的,那么我们碰到的大多数问题,靠人工是无法找到这个规律的。这其中的逻辑是,每次输入有3个值,那么给每个值分配一个权重(三个权重初始化为-1~1的随机值,权重初始化的问题,里面还有很多内容,这里先放放。),通过多次的学习,根据神经网络的输出和期望的输出结果的误差来调整这三个输入值的权重。学习完成后,会将这三个权重调整到最优值。实际测试时,会根据最优的权重输出正确的结果。
训练过程:
1.读取输入,得到输入和权重的点积结果并归一化。
这里某些字符输入不方便,直接借用别人的图片:
公式中weight即是三个值的权重,input是输入的三个值。这个公式即为点积公式;三个输入值,则为三个神经元;
那么,归一化是利用函数Sigmoid实现的:
归一化后,使得点积映射到0~1之间。
这个函数的图像:
最后,得到输出的计算公式为:
2.计算误差,即期望输出和神经网络的输出之间的差值。如果输出为0.6,那我们期望他输出1,则差值为1-0.6
3.计算调整值,比较好的方法是,误差大调整的步长就大,误差小调整的步长就小,也就是调整量和误差成正比。那么,我们通过输出(归一化后),来获取输出在上面曲线的斜率(变化率),再乘误差,则是调整值。
那么,变化率是上面归一化函数的导数:
则调整值:
点积越大,越接近1,斜率越小,调整值越小。误差越大,调整值越大。
4.调整权重,调整值和输入点积,获取各神经元的调整值,原权重加各神经元的调整值,则得到各神经元新的权重。
下面,用aar代码来实现:
今天就到这,先睡了。。。明天继续。。。
import console;
io.open();
var inputNum=3;
var coreWidth=4;
var testInput={1;0;0}
var initWeight=function(num){
math.randomize();
var weight={};
for(i=1;num;1){table.push(weight,2*math.random()-1)}
return weight;
}
var weight=initWeight(inputNum)
io.print("weight:",console.dump(weight));
var infer=function(nodeNum,inputTab,weightTab){
var dot=0;
for(i=1;nodeNum;1){
dot+=inputTab[[i]]*weightTab[[i]]
}
return 1/(1+math.exp(-dot));
}
//-------------------------------------初始化
var core={};
for(i=1;1000000;1){
math.randomize();
var trainInput,transfer={},{};
for(i=1;inputNum;1){
table.push(trainInput,math.random(0,1))
}
var outPut=infer(inputNum,trainInput,weight)
var err=trainInput[[1]]-outPut;
for(i=1;inputNum;1){
table.push(transfer,trainInput[[i]]*err*outPut*(1-outPut));
}
table.push(core,transfer);
if(i>coreWidth){table.remove(core)}
/*
if(i>0 and i%coreWidth==0){//每4次输出后,4个误差和输入卷积,该卷积是孤立的
for(i=1;inputNum;1){
var adjust=0;
for(j=1;#core;1){
adjust+=core[[j]][[i]]
}
weight[[i]]+=adjust;
}
}
*/
for(i=1;inputNum;1){ //每次输出后的误差和之前的3个误差与4个输入卷积,该卷积包含之前所有输入和误差的信息。
var adjust=0;
for(j=1;coreWidth;1){
adjust+=core[[j]][[i]]
}
weight[[i]]+=adjust;
}
/*
io.print("transfer:",console.dump(transfer));
io.print("transfer:"++#transfer);
io.print("lencore:"++#core);
io.print("trainInput:",console.dump(trainInput));
io.print("dot:"++dot);
io.print("core:",console.dump(core));
io.print("weight:",console.dump(weight));
io.print(string.repeat(30,"="));
*/
}
io.print("weight:",console.dump(weight));
io.print("outPut:",infer(inputNum,{0;0;1},weight));
execute("pause")
import console;说说两次代码的差别:
io.open();
var inputNum=3;
var coreWidth=4;
var trainInput={{0;0;1}{1;1;1}{1;0;1}{0;1;1}}
var initWeight=function(num){
math.randomize();
var weight={};
for(i=1;num;1){table.push(weight,2*math.random()-1)}
return weight;
}
var weight=initWeight(inputNum)
io.print("weight:",console.dump(weight));
var infer=function(nodeNum,inputTab,weightTab){ //这个函数两个作用,一是给下面的学习调用来获取输出,二是用来测试
var dot=0;
for(i=1;nodeNum;1){
dot+=inputTab[[i]]*weightTab[[i]]
}
return 1/(1+math.exp(-dot));
}
//-------------------------------------初始化
for(i=1;10000;1){
math.randomize();
var transfer;
var outPut,err=0,0;
for(i=1;coreWidth;1){
transfer=trainInput[[i]]; //取训练数据二维数组中的行,转为一维数组
outPut=infer(inputNum,transfer,weight); //一行输入代入神经网络正向输出,归一化
for(j=1;inputNum;1){
weight[[j]]+=(transfer[[1]]-outPut)*outPut*(1-outPut)*trainInput[[i]][[j]]; //一行输入的误差*斜率,和输入的三个项分别乘积,累加四次到三个权重
}
}
}
io.print("weight:",console.dump(weight));
io.print("outPut:",infer(inputNum,{0;0;1},weight));
execute("pause")