依照固有思维形式,人们总以为人工智能是一个莫测高深的行业,这个行业的人都是高智商人群,无论是写文章还是和人讲话,总是守口如瓶,接着就是蹦出一些“高级”词汇,什么“神经网络”,什么“卷积神经”之类,教人半懂不懂的。尤其 ChatGPT 的风行一时,更加“神话”了这个行业,用鲁迅学生形容诸葛武侯的话来讲:“多智而近妖”。
事实上,依据二八定理,和别的行业一样,人工智能行业内真正顶尖的蠢才也就是 20%,他们具备真正的行业颠覆能力,能够搞出像 ChatGPT 这种“工业革命”级别的产品,而剩下的 80% 也不过就是普通人,每天的工作和咱们这些人一样,干燥且乏味,而之所以会呈现相似“行业壁垒”的景象,是因为这个行业的“黑话”太多了,导致个别人听不懂,所谓“黑话”能够了解为行业术语,搞清楚了这些所谓的行业术语,你会发现,所谓的“人工智能”,不过也就是个“套路活儿”。
本次咱们试图应用“白话文”来描摹人工智能机器学习的底层逻辑,并且通过 Golang1.18 来实现人造神经元的原理。
人造神经元 Neural
当初业内比拟风行的,比方 Transformer 模型、GPT 模型、深度学习、强化学习、卷积神经网络等等,无论听起来如许高端大气上档次,说出大天去,它也是神经网络架构,换句话说它们的底层都一样,类比的话,就像汽车行业,无论是汽油驱动还是电驱动、三缸发动机还是六缸发动机、单电机还是双电机,混合能源还是插混能源,无论汽车主机厂怎么吹牛逼,无论车评人怎么疯狂恰饭,厂商造出来的车最终还是一个最根本的汽车架构,说白了就是一个底盘四个轮儿,你再牛逼,你也逃不出这个根本架构。
所以说,机器学习的根本架构是神经网络架构,神经网络由大量的人工神经元组成,它们联结之后就能够进行所谓的“机器学习”。所以,必须搞清楚什么是神经元,能力弄懂神经网络。
神经元是神经网络架构中最渺小的单位,也是最小的可训练单位,一个根本的神经元构造是上面这样的:
毋庸讳言,大多数人看见这个基本上都会霎时放弃,数学公式几乎就是开发界的洪水猛兽,其劝退能力堪比艾尔登法环中的大树守卫,咱们的机器学习之旅还没开始,就曾经完结了。
那么咱们把这个玩意儿翻译成大多数人能看懂的样子:f(sum( x*w) + b )
x 代表输出的数据,w 代表权重,sum 代表求和,b 代表偏差,f 代表激活函数,最初这个公式运行的后果,就是机器学习的后果。
简略往外头套点数据,比方我心愿机器学习的后果是 10,那么,x、w、和 b 别离应该是什么能力让后果变为 10 呢?如果 x=2 w=4 b = 2 就是咱们想要的后果。这也就是最根本的线性回归,咱们只解决一个维度的数据,因为后果曾经不言而喻了,咱们曾经不须要机器学习了,因为靠猜也能猜出来后果是什么。
然而生产环境中,x 并非是单维度,而是多维度的,比方 x1、x2、x3….. 组成的矩阵,但无论是多维度还是单维度,计算公式用的还是一样的,每一个 x 对应一个权重 w,所以是 xn*wn。
说白了,x 就是一个多维特色,类比的话,如果咱们想让电脑智能识图,比方辨认一只猫,那么 x 就是猫的特色,比方状态、色彩、眼睛、叫声等等,作为多维度的输出特色 x,喂给电脑,让电脑给出辨认后果,这就是简略的机器学习解决分类问题。
这里须要留神的是,x 作为特征参数,并不是越多越好,而是特色越显著越好,举个例子,你想让 AI 去辨认鲁迅的文章,那提供的特色最好应该具备鲁迅文章的特点,而不是全量输出,因为鲁迅就算再“鲁迅”,他写的文字也会和他人反复,也就是说并不是每句话都是他独有的,如果把他所有的文章都喂给电脑,可能就会产生“噪声”,影响机器学习的后果。
另外应该晓得的是,x 参数特色并不是咱们认为的单词或者汉字,而是一串单精度区间在 0 - 1 之间的浮点数字,也就是所谓的“向量”,因为只有数字能力套着神经元公式进行计算。
所以所有的文本特色在进行神经元计算之前,必须通过一些办法进行“向量化”操作。说白了就是把汉字转化为数字,就这么简略。
另外这也就证实了,电脑真的没有思维,它不了解什么是猫,或者谁是鲁迅,它就是在进行计算,而已。
随后是 w,w 指的是权重,权重是指神经元接管到的输出值的重要性,这些输出值通过乘以对应的权重,被加权求和作为神经元的输出。权重值越大,示意该输出在神经元的输入中所占的比重越大。说白了,猫的所有特色的权重并不是对立的,比方黑夜里忽然一个货色跳了进去,你怎么判断它是什么物种?很显著,一声“喵呜”咱们就能够立即判定这是一只猫,所以叫声特色的权重肯定大于其余特色的权重。
最初是 b,也就是偏差 (bias),偏差通常是一个实数,与神经元的权重一样,也是通过训练神经网络而调整的参数。偏差的作用是在神经元的输出上减少一个常量,以调整神经元的激活阈值。如果没有偏差,那么神经元的激活函数将仅仅取决于加权和的值,而无奈产生任何偏移。
说白了,b 就是让 x * w 的值更活一点,让它不是“死”的数。
最初说说 f 也就是激活函数,激活函数通常具备非线性的个性,这使得神经网络可能拟合非线性的简单函数,从而进步其性能和准确度。
说白了,如果没有激活函数,咱们的权重计算就是“线性”的,什么叫线性?如果把 x 特色从 1 开始输出,始终到 100,而后将计算结果绘制成图:
咱们会发现计算结果是一根直线,这显然不合乎客观规律,更合乎生物特色的状态应该是“曲线”,所以说白了,激活函数 f 的作用就是把“直线”变成“曲线”。
最初,咱们把神经元公式革新成不便咱们了解的模式:冀望后果 = 激活函数 (求和 ( 特色 * 权重) + 偏差 )。
如果用代码实现这个公式:
func neuron(inputs []float64, weights []float64, bias float64) float64 {if len(inputs) != len(weights) {panic("inputs and weights must have the same length")
}
sum := bias
for i := 0; i < len(inputs); i++ {sum += inputs[i] * weights[i]
}
return sum
}
这个函数接管两个长度雷同的浮点数数组 inputs 和 weights,以及一个偏置值 bias。它通过将每个输出值乘以其对应的权重,加上偏置值,失去神经元的加权和。最初,函数返回这个加权和作为神经元的输入值。
应用这个函数时,能够将输出数据和权重作为参数传递给它。例如,假如咱们有一个二分类问题,输出数据有两个特色:x1 和 x2,并且咱们有一个蕴含两个权重和一个偏置的神经元。那么能够这样调用神经元公式:
inputs := []float64{x1, x2}
weights := []float64{w1, w2}
bias := b
output := neuron(inputs, weights, bias)
这里返回神经元的输入值,所以,谁说学习人工智能必须得用 Python?咱们就骄傲地应用 Golang。
机器学习,到底怎么学习
机器学习的过程就是上文中神经元公式的应用过程,第一步收集所有的特色数据,而后进行权重调配,最初向量化操作,把文本数据转换为计算机能计算的浮点数,随后加权求和,之后加一个偏差 (bias),最初过一下激活函数,最终失去一个冀望后果,就完事了。
难吗?不难,普通人连猜带蒙也能做。
但事实上,这只是原理,也就是最根本的货色,个别人都能把握,比方打篮球,规定非常简单,外围就是运球和投篮,无反抗下小学生也能霎时把握,但小学生没法去打 NBA 级别的较量,因为很多高端的篮球技术是构筑于运球和投篮的,须要经久不息的练习和本身天才的加成,所以全世界能打 NBA 的就那么几百人,而已。
同理,机器学习的过程也并非如此简略,通过特色输出,通过神经元公式,失去的后果真的肯定是咱们所冀望的后果吗?
其实未必,机器学习还包含两个极其重要的概念:前向流传和反向流传。
前向流传是指将输出数据从神经网络的输出层传递到输入层的过程。在前向流传过程中,输出数据通过神经网络的每一层,每个神经元都会对其进行肯定的加权和激活函数计算,最终失去输入层的输入值。这个过程也被称为“正向流传”,因为数据是从输出层顺次向前流传到输入层。
反向流传是指在前向流传之后,计算神经网络误差并将误差反向流传到各层神经元中进行参数(包含权重和偏置)的更新。在反向流传过程中,首先须要计算网络的误差,而后通过链式法则将误差反向流传到各层神经元,以更新每个神经元的权重和偏置。这个过程也被称为“反向梯度降落”,因为它是通过梯度降落算法来更新神经网络参数的。
说白了,前向流传就是由特色到后果的过程,反向流传则是逆运算,用后果反推过程。
回到分类问题,咱们输出了猫的特色和特色权重,通过计算,后果未必是猫,可能是狗,或者是耗子,也可能是别的什么货色,但这不重要,重要的是咱们须要拿到一个后果的误差,这个误差越小越好,而反向流传就是帮咱们推算误差到底有多大的办法。
而误差的大小就取决于特色的输出,导致机器学习后果谬误的本源是参数,此时,咱们须要调整参数的输出,从而减小误差值,这也就是人工智能行业从业人员常常说的“调参”。
比方,咱们冀望后果是猫,后果计算机返回狗,那么调整参数,后果返回熊猫,那么就阐明调大发了,持续调整,直到计算机返回后果:猫。
在 Golang1.18 中,能够通过以下代码实现反向流传:
func backpropagation(inputs []float64, targets []float64, network *Network, learningRate float64) {
// 1. 前向流传,计算每个神经元的输入值
outputs := feedforward(inputs, network)
// 2. 计算输入层的误差
outputErrors := make([]float64, len(outputs))
for i := range outputs {outputErrors[i] = outputs[i] - targets[i]
}
// 3. 反向流传误差,计算每个神经元的误差值
for i := len(network.layers) - 1; i >= 0; i-- {layer := network.layers[i]
errors := make([]float64, len(layer.neurons))
// 3.1. 计算神经元的误差值
if i == len(network.layers)-1 {
// 输入层的误差
for j := range layer.neurons {errors[j] = outputErrors[j] * sigmoidPrime(layer.neurons[j].output)
}
} else {
// 暗藏层的误差
for j := range layer.neurons {
errorSum := 0.0
nextLayer := network.layers[i+1]
for k := range nextLayer.neurons {errorSum += nextLayer.neurons[k].weights[j] * nextLayer.neurons[k].error
}
errors[j] = errorSum * sigmoidPrime(layer.neurons[j].output)
}
}
// 3.2. 将误差值保留到神经元中
for j := range layer.neurons {layer.neurons[j].error = errors[j]
}
}
// 4. 更新神经网络的权重和偏置
for i := range network.layers {layer := network.layers[i]
// 4.1. 更新权重
for j := range layer.neurons {for k := range layer.neurons[j].weights {
if i == 0 {
// 输出层的权重
layer.neurons[j].weights[k] -= learningRate * layer.neurons[j].error * inputs[k]
} else {
// 暗藏层和输入层的权重
prevLayer := network.layers[i-1]
layer.neurons[j].weights[k] -= learningRate * layer.neurons[j].error * prevLayer.neurons[k].output
}
}
// 4.2. 更新偏置
layer.neurons[j].bias -= learningRate * layer.neurons[j].error
}
}
}
这个函数接管输出数据 inputs、指标数据 targets、神经网络 network 以及学习率 learningRate 作为参数。它首先调用 feedforward 函数进行前向流传,计算每个神经元的输入值。而后,它计算输入层的误差,通过误差反向流传,计算每个神经元的误差值,并将其保留到神经元中。
接下来,函数依据误差值和学习率更新神经网络的权重和偏置。在更新权重时,须要依据神经元所在的层来抉择更新的权重类型(输出层、暗藏层或输入层),而后依据误差值和输出数据或上一层神经元的输入值来更新权重。在更新偏置时,只须要依据误差值和学习率来更新即可。
总的来说,这个函数实现了反向流传的所有步骤,能够用于训练神经网络并进步其准确度和性能。
结语
小道不过三俩句,说破不值半文钱,所谓人工智能机器学习就这么回事,没必要神话,也毋庸贬斥,类比的话,就像餐饮行业的厨师岗,所谓做菜,底层原理是什么?就是食材和火候,把握了做菜的底层原理,就能做出好菜,其余的,比方刀工、色彩等等,不过就是精益求精的货色,而已。
所以机器学习就是做菜,做进去的货色可能不尽如人意,就得不停地调整食材的搭配和火候的大小,所谓机器学习的最重要技巧,其实是特色的提取以及参数的调整,所谓大道至简,必由之路。