这两年ai太火了,眼睁睁看着市面上ai产品的表现跟魔法似的,身为UI仔为此感到深深的焦虑。
虽然打不过但是也加不入,也只能被逼着学习一些。
此处分享一个UI仔也能看得懂的机器学习例子,希望能帮助到基础比我还薄弱的人。
01
设计一个神经网络
以一个极简单例子入手,假设我们现在需要训练一个模型,它需要完成一样最简单的任务:判断输入的两个数字的大小,应该怎么做?
上帝视角
通过上帝视角,我们知道判断x0是否小于x1,就等于函数
所以只需要计算x0-x1的值判断是不是<0就行了。
设计模型
硬套到神经网络上,就是怎么能让神经网络表达出函数f(x0,x1)=x0-x1。
然后我们又知道神经网络实质上做的运算就是将多组输入乘上多组权重再加到一起而已(如下图)
为了让神经网络表达出f(x0,x1)=x0-x1,我们设计一个神经网络:它有2个输入但只有1个神经元。最后这个网络表达运算就是这样的:
怎么让这个函数表达出x0-x1呢?
我们人类的大脑真的很神奇,竟然一眼就看出来只需要
就行了,但也代表这个模型确实可以表达出这个式子。
至此我们解决了模型的设计部分,虽然我们已经知道系数应该是多少,但是怎么让机器也知道才是关键,这部分就是训练模型的环节了。
02
训练一个神经网络
怎么让机器找到正确的方程系数?
不知道大家有没有听说过在有一种排序算法叫做猴子排序,也叫做bogo排序,是一种很扯淡的排序算法,它的伪代码是这样的的:
while arr不是有序的:
随机排列(arr) =
这个排序虽然看着很扯,但是你我都知道其实它是能工作的,只是有点慢而已。
训练神经网络也可以借用这个思想,就是说只要我们有能力判断机器给出的答案对不对这件事情,仅仅是不断用随机数去生成函数,假以时日我们也能令到机器找到正确的方程系数的。
而验证这件事情很简单,我们有训练集数据,将训练集数据套进模型比较正确值跟输出是否一致就行了。
while f(训练集) 没有全对:
w0 = 随机值
w1 = 随机值
b = 随机值
启发式的遍历方法
这个算法看着就很耗时。退而求其次,如果只要求找到近似解的话,可以改成用随机数去碰撞很多次,只保留最接近的结果。
# 遍历很多次,记录下最好的方程系数
for i in range(10000):
w0 = 随机值
w1 = 随机值
b = 随机值
cur_error = ERROR(w0,w1,b)
if cur_error < min_error:
min_error = cur_error
best_w0 = w0
best_w1 = w1
best_b = b
这个算法明显看着就比前一个算法好多了。
怎么衡量误差?
上面这个启发式算法依赖一种衡量误差的方法。算法里我们的误差是用来排序的,所以它需要具备可比较性,而且它在数值上的含义应该是函数偏离正确值越远数值越大。
这样的误差函数很容易被我们设计,两个数值相减取绝对值就行了,太简单了:
这个函数图像画出来大概会长这样:
梯度下降
将误差绝对值的函数图像画出来之后发现有些规律:我们要找到的其实是函数的谷底,也就是y=0(就是误差为0)时x的值是多少。
如果我们从一个随机的处开始,是不是判断一下当前点的误差跟(X0+一点点)谁大谁小,就知道应该将往哪个方向移动了,这个算法学名叫做梯度下降。
x = 随机值
for i in range(10000):
right_y = ERROR(x + 0.000001) # 右边的误差
y = ERROR(x)
if right_y < y:
# 如果右边误差小,就往右移动
x += 0.000001
else:
x -= 0.000001
迭代很多次之后,x肯定就是在谷底震荡,但是不会偏离太远。
自适应步长
上面这个算法有个问题,移动的步长是固定的,步长太长的话在谷底的时候必然来回震荡永远无法收敛,更好的做法是采用自适应步长。为了使用自适应步长,我们需要对误差做一些处理。
简单地给误差加一个平方运算让结果对自变量更敏感一些,令到原本的直线变成了曲线,也为误差函数引入了一个额外的信息:变化率
函数图像变成了这样:
观察图像可以发现,曲线中每点的斜率是不一样的,斜率的大小表示该点偏离中心的程度。拥有这个信息后,每次迭代可以根据当前点的变化率调整步长,令到迭代过程中呈现出:偏离中心时步长大,接近中心时步长小。
根据这个指导思想又可以用来改改我们的代码了:
x = 随机值
for i in range(10000):
left_y = ERROR(x - 0.000001)
right_y = ERROR(x + 0.000001)
误差的斜率 = (right_y - left_y) / 0.000001
x -= (误差的斜率 * 0.000001)
这个方法肯定可以让我们代码中的x更快的收敛到中心位置。
梯度
上面的代码还有个问题没解决,就是我们的函数是有3个参数的,图像是个3维图像,x是个向量。对于一个3维向量来说哪里有什么往左走往右走的。
到这里需要借助数学工具了,众所周知1维函数在某点变化率叫斜率,高维函数叫做梯度。1维函数的变化率是个比率、是一个数值,高维函数的变化率是一组比率、是一个向量,求它的时候需要每个分量分开求,叫做偏导数。
不过我不太会求导数更别说什么偏导数了,所以我选择了问gpt:
问:
y=f(w0,w1,b)=w0x0+w1x1+b
ERORR(w0,w1,b)=(y-y`)^2
这个ERROR函数的梯度是多少维的向量?每个维度怎么求?
答:
ERROR函数的梯度是一个三维向量,每个维度的梯度分别为:
∂ERROR/∂w0 = 2(y - y') * x0
∂ERROR/∂w1 = 2(y - y') * x1
∂ERROR/∂b = 2(y - y')
这些梯度向量表示了损失函数相对于每个参数的变化率。我们可以使用这些梯度来更新模型的参数,以最小化损失函数。
知道梯度怎么求之后,我们再改改我们的代码,它就能处理高维情况且变得真实可以跑了
代码
import random
# 固定随机数种子,确保每次运行结果一致
# 生成训练数据
train_data_size = 100
train_data = []
# 生成100组训练用的数据,每项是一个三元组:(x0, x1, x0 - x1)
for i in range(train_data_size):
x0 = random.uniform(0, 1)
x1 = random.uniform(0, 1)
x1, x0 - x1))
# 学习率
learnning_rate = 0.1
# 随机初始化参数
w0 = random.uniform(0, 1)
w1 = random.uniform(0, 1)
b = random.uniform(0, 1)
# 执行1000次梯度下降迭代
for step in range(1000):
# 对所有数据求梯度,然后求平均梯度,用来修正系数
dw0 = 0
dw1 = 0
db = 0
for i in range(train_data_size):
x1, y_real = train_data[i]
y = w0 * x0 + w1 * x1 + b # f(W,b)
error = y - y_real
# 关于w0的变化率 = 2(y - y') * x0
dw0 += 2 * error * x0
# 关于w1的变化率 = 2(y - y') * x1
dw1 += 2 * error * x1
# 关于b的变化率 = 2(y - y')
db += 2 * error
# 求平均
dw0 /= train_data_size
dw1 /= train_data_size
db /= train_data_size
# 往梯度的反方向走一点点
w0 -= learnning_rate * dw0
w1 -= learnning_rate * dw1
b -= learnning_rate * db
# 打印最终的参数
{w0}, w1: {w1}, b: {b}") # 应该接近[1, -1], 0 :
注:发现了没?代码里其实都没有用到所谓的误差的平方,定义它的作用只是为了求梯度而已。
可以把代码拷下来跑一下,它最后会输出:
代表机器通过自己找到了正确答案。
至此,大快人心,我们拥有3个参数的"大"模型训练完成了!
关于LitGate
大家好,我是LitGate,一个专注于AI创作的游戏社区。我们的新版官网已经上线✨你可以在里面找到各种AI创作的实操案例,以及已经沉淀的AI游戏创意demo,相信一定能让你大开眼界!
我们还有一个讨论群📣,如果你对AI创作感兴趣,或者有什么问题想要咨询,欢迎加入我们的讨论群,和大家一起交流学习!(PS:目前群内人数较多,为了有一个优质的讨论环境,请各位添加社区管理员企业微信账号邀请入群
更多精彩活动和功能筹备上线中,敬请期待~
关注我们,一起探索AI创作的无限可能吧!
新版官网地址:www.litgate.ai