感知机
# 感知机
感知机是最简单的神经网络,像一个 “二分类开关”:输入数据后,它会判断数据属于 “类别 1” 还是 “类别 0”(比如判断图片里是猫还是狗)。
核心公式:y = wx - b
- x:输入数据(比如一张图片的亮度值,假设是单个数字)
- w:权重(相当于 “重视程度”,w 越大,x 对结果影响越大)
- b:偏置(相当于 “门槛”,调整判断的基准线)
- y:计算结果(但最终输出需要用 “激活函数” 变成 0 或 1)
激活函数,把结果变成分类,因为我们需要的是 “是 / 否” 的判断,所以用阶跃函数:
- 如果 y ≥ 0 → 输出 1(属于类别 1)
- 如果 y < 0 → 输出 0(属于类别 0)
感知机通过调整 w 和 b 来提高判断准确性,核心是 “错了就改”:
- 用当前的 w 和 b 预测结果
- 如果预测错了,就按规则调整 w 和 b
- w 新 = w 旧 + 学习率 × (正确答案 - 预测结果) × x
- b 新 = b 旧 - 学习率 × (正确答案 - 预测结果)
- 学习率:控制每次调整的幅度(比如 0.1,防止调整太猛)
简单示例:
public class Perceptron {
// 初始化权重和偏置(随便给个初始值)
double w = 0.1; // 权重
double b = 0.1; // 偏置
double learningRate = 0.1; // 学习率
// 激活函数:把计算结果转成0或1
int activate(double y) {
return y >= 0 ? 1 : 0; // 阶跃函数
}
// 预测:输入x,输出0或1
int predict(int x) {
double y = w * x - b; // 核心公式
return activate(y);
}
// 学习:用正确答案调整w和b
void train(int x, int label) {
int prediction = predict(x); // 先预测
int error = label - prediction; // 计算误差(正确-预测)
if (error != 0) { // 只有预测错了才调整
w += learningRate * error * x; // 调整权重
b -= learningRate * error; // 调整偏置
}
}
public static void main(String[] args) {
Perceptron p = new Perceptron();
// 训练数据:x和对应的正确答案(x≥5→1,否则0)
int[][] data = {{3, 0}, {6, 1}, {2, 0}, {7, 1}, {4, 0}};
// 训练10次(多练几次才能学好)
for (int i = 0; i < 10; i++) {
for (int[] d : data) {
p.train(d[0], d[1]); // 输入x和正确答案
}
}
// 测试一下
System.out.println("预测x=5:" + p.predict(5)); // 应该输出1
System.out.println("预测x=4:" + p.predict(4)); // 应该输出0
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
- 初始化:w 和 b 一开始随便设(0.1),学习率 0.1(小一点稳)
- activate 方法:用阶跃函数把 y 转成 0 或 1
- predict 方法:代入公式 y=wx-b,再用激活函数得结果
- train 方法:先预测,算误差(正确答案 - 预测结果); 如果误差≠0(预测错了),就按公式调整 w 和 b
- main 方法:准备训练数据(x 和正确答案); 训练 10 轮(反复学 10 次,让 w 和 b 更准); 测试结果:x=5 应该输出 1,x=4 输出 0
比如当 x=6(正确答案 1),如果一开始预测成 0;
误差 = 1-0=1
w 会变大(因为 w += 0.1×1×6)→ 下次 x=6 时,y=wx-b 会更大,更容易≥0→输出 1
b 会变小(因为 b -= 0.1×1)→ 门槛降低,更容易输出 1
反过来,如果预测错成 1,w 会变小,b 会变大,让下次预测更准。
已知某楼市房价有房价表如下:
| 面积 | 价格 |
|---|---|
| 50 | 5003 |
| 60 | 6005 |
| 70 | 7007 |
要得到在 80 平米的房价是多少?这里不是一个准确的价格计算,而是预测一个价格。预测是通过对已有数据进行训练,得到合适的参数,然后根据参数来预测未来的值。
可以使用公式 y = wx - b (单变量线性回归) 来进行预测,其中 w 和 b 是参数,y (价格) 是预测值,x (面积) 是输入值。
# 导数
导数本质是 “变化率”—— 描述一个东西随另一个东西变化的 “快慢和方向”。
你开车时,“速度” 就是 “路程对时间的导数”:
- 速度 = 50km/h → 表示 “时间每增加 1 小时,路程增加 50 公里”(变化率是 50,正方向)
- 速度 = -20km/h → 表示 “时间每增加 1 小时,路程减少 20 公里”(倒车,变化率是 - 20,反方向)
咱们之前的感知机用的是 “阶跃函数”(输出非 0 即 1),但它有个缺点:没法精确调整 w 和 b 的幅度。比如:
- 正确答案是 1,预测成 0(误差 = 1)→ 不管误差多 “离谱”(比如 wx-b=-100 还是 wx-b=-1),都用同样的规则调整 w 和 b。
- 但实际上,wx-b=-100 比 wx-b=-1 “错得更离谱”,应该调整得更狠一点才对。
这就像老师批改作业:不管你错了 1 题还是错了 10 题,都罚抄 1 遍 —— 显然不够合理。我们需要一种能 “根据错误程度调整惩罚力度” 的方法。这时候,导数就能帮我们计算 “该调整多少”—— 错得越离谱,调整幅度越大;错得少,调整幅度小。
为让 “错误程度” 可衡量,我们把激活函数换成 “Sigmoid 函数”(它的输出是 0 到 1 之间的连续值,不是非 0 即 1)。即公式:
- wx-b=-100 → 输出≈0(错得离谱)
- wx-b=-1 → 输出≈0.27(错得不轻)
- wx-b=0 → 输出 = 0.5(半对半错)
- wx-b=1 → 输出≈0.73(接近正确)
- wx-b=100 → 输出≈1(几乎正确)
这样一来,“预测值” 和 “正确答案” 的差距就能精确衡量了,相当于知道了为了达到某件事,还差百分之多少。
既然 Sigmoid 函数帮我们区分了程度,那么我们还差具体需要调节多少,这时就需要用 "损失函数" 来计算。比如用 “平方误差损失”
- 正确答案 1,预测 0.27 → Loss=(1-0.27)²≈0.53(错得不轻)
- 正确答案 1,预测 0.01 → Loss=(1-0.01)²≈0.98(错得离谱)
Loss 越大,说明错误越严重。我们的目标就是让 Loss 尽可能小(也就是让预测越来越准)。注意,这里的输出结果是做为一个可量化的值而已,不是实际误差,他也只是告诉我们值越大,越偏离目标,而且越大越要先纠正。
没有 Sigmoid 的话,你只能得到 “0 或 1” 的预测(阶跃函数),没法评估“错误程度”;没有损失函数的话,你只有 “预测值”,没法直接知道 “这个值离正确答案差多少(量化值)、要不要调整 w 和 b”。
我们知道 Loss 大小后,就要调节参数 w 和 b,这时就要用上导数,导数的作用是告诉我们 “当 w(或 b)稍微变化一点点时,Loss 会怎么变?”
- 如果 “Loss 对 w 的导数” 是正数 → 说明 w 增大时,Loss 也会增大(越调越错)→ 要减小 Loss,必须让 w 减小。
- 如果 “Loss 对 w 的导数” 是负数 → 说明 w 增大时,Loss 会减小(越调越对)→ 要减小 Loss,必须让 w 增大。
z = wx - b(感知机的核心计算结果),而我们最终要调整的是 w 和 b。但是 z 是 w 和 b 的 “中间人”——w 和 b 的变化会先影响 z,z 的变化再影响预测值,预测值的变化最后影响损失 Loss。他们之间关系如下:
- 输入 x:已知的数(比如例子中的 x=6);
- 参数 w 和 b:需要调整的数(比如初始 w=0.5,b=2);
- z:z = wx - b(w 和 b 的 “孩子”,由 w 和 b 决定);
- 预测值 prediction:由 z 决定,用 Sigmoid 函数算:
; - 损失 Loss:由预测值和正确答案 label 决定,用平方误差:Loss = (label - prediction)²。
所以:
- “Loss 对 z 的导数(dLoss/dz)”→ 问:“z 变 1 小点点,Loss 会变多少?”
- “z 对 w 的导数(dz/dw)”→ 问:“w 变 1 小点点,z 会变多少?”
- 把这两个结合起来,就能知道:“w 变 1 小点点,Loss 会变多少?”(这就是 Loss 对 w 的导数)。
计算导数公式:
- prediction:
d (Loss)/d (prediction) 是损失函数对预测值的导数,Loss 是 “(label - prediction) 的平方”,也就是 Loss = (A)²,其中 A = label - prediction,根据基本求导规则:(A²)’ = 2×A×A’(平方的导数是 2× 本身 × 本身的导数),这里 A 对 prediction 的导数是 A’ = -1(A=label+(−1)×prediction 这里,prediction的系数就是-1(即 “prediction 前面的系数是 -1”)),所以:dLoss/d (prediction) = 2×(label - prediction)×(-1) = 2×(prediction - label) - z:
d (prediction)/dz 是预测值对 z 的导数,prediction 是 sigmoid 函数,用求导规则推导后(数学家已经算好),结果非常简洁:等于 “prediction ×(1 - prediction)”,不用记推导过程,记住结论就行。 - dz:
dLoss/dz 损失函数对 z 的导数(两者的乘积,链式法则)dLoss/dz = dLoss/d(prediction) × d(prediction)/dz - dw:
dz/dw = x,z 的表达式是 z = wx - b,把 x 和 b 看作固定值,w 是变量,就像 y = 5×x + 3 中,y 对 x 的导数是 5(x 的系数),z 里 w 的系数是 x,所以 z 对 w 的导数就是 x。 - db:
dz/db = -1,还是 z = wx - b,把 w 和 x 看作固定值,b 是变量,b 前面的符号是 - 1,就像 y = 3 - 2x 中,y 对 x 的导数是 - 2(x 的系数),所以 z 对 b 的导数就是 - 1。
public class PerceptronWithDerivative {
double w = 0.1;
double b = 0.1;
double learningRate = 0.1; // 学习率
// Sigmoid激活函数
double sigmoid(double z) {
return 1 / (1 + Math.exp(-z)); // e^(-z)用Math.exp计算
}
// 预测:输出0-1之间的概率
double predict(int x) {
double z = w * x - b;
return sigmoid(z);
}
// 用导数训练
void train(int x, int label) {
double prediction = predict(x); // 预测值(0-1)
double loss = Math.pow(label - prediction, 2); // 损失函数
// 打印损失,观察变化(新增代码)
System.out.println("当前损失:" + loss);
// 计算导数(按上面的公式)
// z 因为是中间变量
double dLoss_dz = 2 * (prediction - label) * prediction * (1 - prediction);
double dLoss_dw = dLoss_dz * x; // z对w的导数是x
double dLoss_db = dLoss_dz * (-1); // z对b的导数是-1
// 用导数调整w和b(梯度下降)
w -= learningRate * dLoss_dw;
b -= learningRate * dLoss_db;
}
public static void main(String[] args) {
PerceptronWithDerivative p = new PerceptronWithDerivative();
int[][] data = {{3, 0}, {6, 1}, {2, 0}, {7, 1}, {4, 0}};
// 训练100次(Sigmoid需要多练几次)
for (int i = 0; i < 100; i++) {
for (int[] d : data) {
p.train(d[0], d[1]);
}
}
// 测试:输出接近1或0的值
System.out.println("预测x=5:" + p.predict(5)); // 应该接近1
System.out.println("预测x=4:" + p.predict(4)); // 应该接近0
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
梯度下降公式:参数新 = 参数旧 - 学习率 × 梯度(导数)
偏导就是当一个变量(比如 Loss)同时受多个变量(比如 w 和 b)影响时,固定其他变量不变,只看一个变量对它的影响,这时候的导数就是 “偏导数”。比如
- 计算 dLoss/dw 时,我们固定 b 不变,只看 w 变化对 Loss 的影响 → 这是 Loss 对 w 的偏导数(记为∂Loss/∂w)
- 计算 dLoss/db 时,我们固定 w 不变,只看 b 变化对 Loss 的影响 → 这是 Loss 对 b 的偏导数(记为∂Loss/∂b)。
代码表现:
// 计算Loss对w的偏导数(固定b不变)
double dLoss_dw = dLoss_dz * x;
// 计算Loss对b的偏导数(固定w不变)
double dLoss_db = dLoss_dz * (-1);
2
3
4
导数(普通导数):单变量函数
- 函数:y = 2x(y 只由 x 决定,没有其他变量)
- 导数:dy/dx = 2 → 意思是 “x 每变 1,y 就变 2”(只有 x 一个变量,不用考虑其他)
偏导数:多变量函数
- 函数:z = 2x + 3y(z 由 x 和 y 两个变量决定)
- 偏导数 1(对 x):∂z/∂x = 2 → 固定 y 不变,x 每变 1,z 变 2
- 偏导数 2(对 y):∂z/∂y = 3 → 固定 x 不变,y 每变 1,z 变 3