想要直接获取所有代码和结果,或者想在Jupyter Notebook上直接打开可以点击这里,欢迎在评论区吐槽~

信息瀑布实验

使用Python来探索最优策略

实验介绍

信息瀑布实验是经典的行为金融学实验。

实验背景设定

每组六个人。对每组来说,有两个杯子,一个红色杯子,一个蓝色杯子。其中,红色杯子里装有两个红球一个蓝球。蓝色杯子里装有两个蓝球一个红球。

实验流程

实验分为六轮。每一轮当中,计算机会随机挑选一种颜色的杯子。被试者不知道计算机选择的杯子是哪一个。

计算机然后会给每个人发一个从该杯子里取出的球(每发完一个又放回去)。

每组六个人有先后次序,来猜测计算机挑选的杯子颜色。第一个决策者只能从球的颜色来判断杯子的颜色。而第二个就可以通过第一个人的决策加上自己的球的颜色来决策,以此类推,排在后面的人便可以依照前面的人的选择结果来判断杯子的颜色。每一轮结束,杯子的真实信息会公布。

经济理论

主要有羊群行为和信息层叠模型。

羊群行为

羊群行为体现在信息瀑布实验和银行挤兑实验当中。羊群行为也就是“从众行为”,是指行为上的模仿和一致性。该词源于生物学对动物聚群特征的研究。
引申到人类社会,便表现为采用同样的思维活动,进行类似的行为,心理上依赖于和大多数人一样思考、感觉、行动,以减少采取行动的成本,获得尽可能大的收益。延伸到金融市场,借用生物学上羊群的群聚行为的概念,指代投资者在信息不确定的情况下,行为受到其他投资者的影响,模仿他人决策,或者过多依赖于舆论,而不考虑私人信息的行为。

信息层叠模型

信息层叠模型认为,一种投资策略会随着时间的流动而变动,如果先行者根据其私人信息采取了行动,后继者就会根据他们的行动来推测信息,借以修正自己的先验信息。如果后继者在修正过程中完全忽视先验信息或者给予先验信息过小的权重,就容易产生模仿先行者的行为。后继者赋予先验信息的权重大小取决于他对先验信息准确度的信心。
信息瀑布中,人们的预期是用贝叶斯规则得到的。贝叶斯规则是指人们根据新的信息从先验概率得到后验概率的方法。

实验复现

代码流程

我们需要找出最优策略,所以写下模拟实验的代码,来检验每一种策略的正确率是多少,从而指导我们判断出群体最理性的行为,使得猜对的概率尽可能大。

引入库

我们需要numpy来加速随机数的生成以及列表的处理。

使用pandas来以列表的形式记录实验结果。

使用matplotlib来可视化结果。

如果没有的话请使用pip install package_name来安装。

1
2
3
4
5
6
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import fractions
%matplotlib inline
%config InlineBackend.figure_format = 'retina'

定义类

采用面向对象的程序设计思想,来尽可能降低代码的阅读难度。基于同样的目的,对于类的内部变量也没有封装。

我们只需要定义一个内部变量颜色。

1
2
3
class Ball:
def __init__(self, color):
self.color = color

杯子

我们需要根据杯子的颜色来建立杯子,还要定义里面装了三个球,球的颜色按照实验的要求。

除了构造函数以外,定义了一个pick()函数,表示从杯子里随机抽一个球。返回的结果是字符串,表示颜色。

1
2
3
4
5
6
7
8
9
10
class Cup:
def __init__(self, color):
self.color = color
if color == 'Red':
self.balls = np.array([Ball('Red'), Ball('Red'), Ball('Blue')])
else:
self.balls = np.array([Ball('Red'), Ball('Blue'), Ball('Blue')])

def pick(self):
return self.balls[np.random.randint(0, 3)].color

杯子集合

总共有两个杯子,一红一蓝。杯子集合就是这么定义了。

还有一个函数random_pick()表示随机抽选一个杯子。返回的对象是Cup

其实也可以不要这个类,直接写在test函数中也可,看哪种方便你理解了。

1
2
3
4
5
6
class Cups:
def __init__(self):
self.Cups = np.array([Cup('Red'), Cup('Blue')])

def random_pick(self):
return self.Cups[np.random.randint(0, 2)]

人的构造函数要指定一个order,即这个实验中给定的次序。因为不同的次序所作出的决策是不同的。

人的内部变量有次序,有抽到的球的颜色,有选择的颜色,还有选择的是否正确(和杯子颜色相同)。

其中要值得关注的是decide(info)函数。这表示了你所定义了的人的策略行为。它需要一个info,表示前人的选择结果。你可以随意改变其中的策略,来观察结果。method代表第几个方法。我预置了三种方法,第一种是优先考虑个人最优的方法(即个人的正确率最大),第二种是优先考虑群体最优的方法。
第三种用于个人最优的对照。

1
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
50
51
52
53
54
55
56
57
class Person:
def __init__(self, order, Cup):
self.order = order
self.choice = None
self.right = False
self.cupcolor = Cup.color
self.picked = Cup.pick()

# TODO:feel free to add methods
def decide(self, info, method):
if method == 1: # 优先考虑个人最优的情况
# 前两人选择自己抽到的颜色
if self.order == 1 or self.order == 2:
self.choice = self.picked
# 后人根据前两人来选择自己的颜色
else:
if info[self.order-2] == info[self.order-3]:
self.choice = info[self.order -2]
else:
self.choice = self.picked
if method == 2: # 优先考虑最后一人最优的情况
if self.order != 6:
self.choice = self.picked
else:
info.append(self.picked)
if info.count('Red') > info.count('Blue'):
self.choice = 'Red'
else:
self.choice = 'Blue'
if method == 3: # 作为一的对照组
# 前三人选择自己抽到的颜色
if self.order <= 3:
self.choice = self.picked
# 后人根据前三人来选择自己的颜色,完全不看自己的颜色
else:
info_temp = info[-3]
if info_temp.count('Red') > info_temp.count('Blue'):
self.choice = 'Red'
else:
self.choice = 'Blue'
if method == 4: # 前两人按照自己的选,后面人根据前面的人选,如果前面的总人数五五开,按照自己的选,否则按照前面的人选。
if self.order == 1 or self.order == 2:
self.choice = self.picked
else:
if info.count('Red') > info.count('Blue'):
self.choice = 'Red'
if info.count('Red') < info.count('Blue'):
self.choice = 'Blue'
else:
self.choice = self.picked
# if method == 5 :
# it's your'turn now!

def strategy(self, info, method):
self.decide(info, method)
if self.choice == self.cupcolor:
self.right = True

策略分析

仔细思考我所给出method==1的策略。

  1. 当我所在的位置是第一个人的时候,我会选择自己抽到的颜色。显然,我的正确率有2/3

  2. 当我所在的位置是第二个人的时候,我还是会选择自己抽到的颜色。因为如果我和前人颜色相同必选前人,答对的概率为4/5。而不同的时候,我选哪种颜色的概率都是1/2。为了告诉下一个人我和前人颜色不同,所以坚守自己的颜色可以更偏向群体最优。

  3. 当我所在的位置是第三个人的时候,如果前两人颜色相同,我会跟从他们的选择。如果两人颜色不同,我会坚定自己的选择。为什么是这样的结果?这是我经过概率论的推导得出的。不会算?没关系,这就是写这篇Notebook的目的。你大可以根据你所认为的最优解调整策略,并观察结果。

  4. 当我所在的位置是第四个人的时候,如果前两人颜色相同(即第三个人也相同),我会选择相同的颜色。如果两人颜色不同,我会选择自己的颜色。

  5. 当我所在的位置是第五人的时候,如果三四两人颜色相同,跟从他们的选择。如果三四两人颜色也不同,坚守自己的选择。

  6. 当我所在的位置是第六人的时候,如果四五人颜色相同,跟从他们的选择。如果四五人颜色不同,坚守自己的选择。

总的说来,就是前两人选自己的颜色,后面的人根据前两人是否相同而选择是否跟从他们。注意,必须人人按照这个规则,才可能得到最优群体解(最后一个人概率最大)。如果中间有一人打破,那么下一个人的最优解会发生变化。这经过了复杂而严密的推导——因为到后期所发生的分支实在是太多了。

是不是越往后越绕,越乱?是时候更改代码验证你自己的策略啦!

method==2时候的策略是前面五个人都遵照自己的意思,第六个人来判断,很明显是一种对最后一人最优的情况。

method==3时候的策略是对于第一种策略的怀疑,可能你会觉得要前三个人都相同我才能更好地符合个人理性。是这样吗?我们实验来证明。

你可能会怀疑,因为第四人或第三人的策略已经将前人的全部策略考虑进去了,所以我们更关注接近次序后面的人的选择。

method==4时候的策略前两人按照自己的选,后面人根据前面的人选,如果前面的总人数五五开,按照自己的选,否则按照前面的人选。

我知道你可能仍然在怀疑,而它已经列入method==5 里了。

单次Test

Test类代表一次实验,需要传递杯子集合,然后抽取一个杯子。接着,自身创建一个人群,人群包含6个人。最后还需要一个信息集。信息集记录前面人的选择。
Test的核心函数是test(),每一个人根据策略选择,然后添加进info当中。test_result()返回了选择的结果,是一个dataframe类型。最后一个print_result()函数只是单纯为了检验Test类写得是否正确,是可以被删去的。

1
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
class Test:
def __init__(self, Cups):
self.cup = Cups.random_pick()
People = []
for i in range(1, 7):
People.append(Person(i, self.cup))
self.People = np.array(People)
self.info = []

def test(self,method):
for i in range(0, 6):
self.People[i].strategy(self.info,method)
self.info.append(self.People[i].choice)

def test_result(self,rounds,method):
self.test(method)
data = pd.DataFrame([[rounds, 1, self.People[0].picked, self.People[0].choice, self.People[0].right]])
for i in range(1, 6):
choice = pd.DataFrame([[rounds, i + 1, self.People[i].picked, self.People[i].choice, self.People[i].right]])
data = data.append(choice)
return data

def print_result(self):
print("Cup Color " + self.cup.color)
print("Person id " + " pick " + " claim " + " correct? ")
for i in range(0, 6):
print(" " + str(i) + " " + self.People[i].picked + " " + self.People[i].choice + " " + str(self.People[i].right))

检验代码是否正确

先检验一次实验的结果。下面代码可以反复运行,看看实际结果是否符合我们要求的结果。这里使用的是Medthod 1。

1
2
3
4
cups = Cups()
test = Test(cups)
test.test(1)
test.print_result()
1
2
3
4
5
6
7
8
Cup Color Blue
Person id pick claim correct?
0 Blue Blue True
1 Blue Blue True
2 Blue Blue True
3 Red Blue True
4 Blue Blue True
5 Blue Blue True

多次测试

Bingo! 接下来需要反复测试并统计结果。我们定义一个Multi_test类。

1
2
3
4
5
6
7
8
9
10
class Multi_test:
def __init__(self, num, method):
self.cups = Cups()
self.data = pd.DataFrame(Test(self.cups).test_result(1,method))
for i in range(1, num):
self.data = self.data.append(Test(self.cups).test_result(i+1,method))
self.data.columns=['round', 'order', 'picked', 'choice', 'right']

def result(self):
return self.data

检验代码是否正确

先检验一次实验的结果。反复测试输出的结果是否符合预期,这里简单重复100次实验,使用方法一。

1
2
test = Multi_test(100,1).result()
test.head(12)
round order picked choice right
0 1 1 Red Red True
0 1 2 Red Red True
0 1 3 Red Red True
0 1 4 Red Red True
0 1 5 Red Red True
0 1 6 Red Red True
0 2 1 Red Red True
0 2 2 Blue Blue False
0 2 3 Blue Blue False
0 2 4 Red Blue False
0 2 5 Blue Blue False
0 2 6 Blue Blue False

开始多轮测试

定义Medthod_Test,我们希望给定epoch和method,自动绘制出结果和显示比例。

我们需要什么样的统计结果呢?

我们需要看六个次序的不同的人,他们判断正确的人有多少。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Method_Test:
def __init__(self, method, epoch):
Test = Multi_test(epoch, method).result()
self.grouped = Test[Test['right'] == True].drop(['round', 'picked', 'choice'], axis=1).groupby('order').count()
self.epoch = epoch
self.whole_right = len(Test[Test['right'] == True])

def plot(self):
self.grouped.plot(kind='bar', ylim=(0, self.epoch), figsize=(13, 6))

def ratio(self):
self.grouped['right'] = self.grouped['right'] / self.epoch
return self.grouped

def ratio_whole(self):
print(self.whole_right/epoch/6)
定义测试轮数

我们测试50000轮。使用head()函数来检查是否运行正确。50000这个数字可以随意更改。越大越精确,同时运行速度越慢。

1
epoch = 50000

方法一:

1
2
test_1 = Method_Test(1,epoch)
test_1.plot()

方法一

我们可以看到,排在第一二次序的人正确率是非常接近2/3的。从第二到第三的正确率大幅增加。在第三到第六次序依次几乎不变。我们用比例进行更好地展示。

1
test_1.ratio().head(6)
right
order
1 0.66850
2 0.66230
3 0.73784
4 0.73862
5 0.75430
6 0.75564
1
test_1.ratio_whole()
1
0.7195333333333332

这是一个使得自身正确率最大的策略。其中,所有人的准确率综合是大于0.71的。

方法二(最后一人正确率优先)

1
2
test_2 = Method_Test(2,epoch)
test_2.plot()

方法二

可以看到,这种策略的结果是前几位正确率在2/3左右,最后一位的结果大幅增加,符合我们的预期。

1
test_2.ratio().head(6)
right
order
1 0.66658
2 0.66892
3 0.66870
4 0.66698
5 0.66396
6 0.78950
1
test_2.ratio_whole()
1
0.68744

但是这种策略的综合正确率只有0.68左右。

方法三

1
2
test_3 = Method_Test(3,epoch)
test_3.plot()

方法三

1
test_3.ratio().head(6)
right
order
1 0.66656
2 0.66930
3 0.66230
4 0.66656
5 0.66930
6 0.66230
1
test_3.ratio_whole()
1
0.6660533333333333

实验表明,这种策略看起来很美好,实际上让所有人的正确率都很低,和按照自己球颜色的一样,大家都是2/3的概率

方法四

1
2
test_4 = Method_Test(4,epoch)
test_4.plot()

方法四

1
test_4.ratio().head(6)
right
order
1 0.66914
2 0.66736
3 0.70686
4 0.70660
5 0.70714
6 0.70654
1
test_4.ratio_whole()
1
0.69394

这个结果算是相当令人惊愕了。考虑了之前所有选择,被给予厚望的方法四效果仅仅比按照自己颜色选好了一点点。虽然参照以往的经验,确实是让每个人的正确率稳步提升,但是提升幅度实在是太小了。这也用实验印证了最初的策略分析内容。

神经网络

更好的测试方法?

我们可以利用深度学习中的感知器神经网络来自动最优化出最优参数。

方法是这样:

  1. 首先将一种颜色定义为1,另一种定义为-1
  2. 先只考虑第二人的情况。
  3. 我们考虑两个变量,分别是第一个人的选择和自身所抽中的球的颜色,当然都是以1,-1表示的。
  4. 任意给定两个权重,譬如0.25,0.75(按照假定,应该是越往后次序抽到的颜色权重越重要,所以这样假设,当然也可以随机假设)。这两者对应前面两个变量的系数,也就是权重。将这两个权重分别与前两个变量乘起来,得到的和,就是对于颜色的预测。预测值大于0代表大于1的颜色,小于0代表小于-1的颜色。还有一个bias可以理解为截距。

w1x1+w2x2+b=predictionw_1x_1 + w_2x_2+b = prediction

  1. 定义一个损失函数,代表预测颜色和实际颜色不一致的错误率。
  2. 不断调整权重,使得预测结果尽可能地好。

如果拿第三个人来举例,如果第一个选择了红色(1),第二个人选择了蓝色(-1),你自己抽到了蓝色(-1)。初始化的权重是0.25,0.25,0.5。
所以你对于颜色的结果预测是

P^=1×0.25+1×0.25+1×0.5=0.5<0 \hat{P} = 1 \times 0.25 + -1 \times 0.25 + -1 \times 0.5 = -0.5 < 0

小于0,即认为是蓝色。

损失函数通常使用交叉熵损失函数,基于极大似然法的思想。它不仅衡量了猜对的个数,而且要求,颜色为1的预测结果越接近1的,比预测结果在0.9左右的更好。这要求一个sigmoid函数将预测值映射到(0,1)的概率上。

L(y^,y)=ylog(y^)(1y)log(1y^)L(\hat{y},y) = -ylog(\hat{y}) - (1-y)log(1-\hat{y})

在感知器模型当中,损失函数只是简单的

L(w,b)=xiMyi(w×xi+b) L(w,b) = - \sum_{x_i \in M} y_i(w \times x_i + b)

调整权重的算法这里使用简单的单层感知器。使用随机梯度下降法,找到一个预测错的点,然后

w=w+yi×xi×ηw=w+y_i\times x_i \times \eta

b=b+yi×ηb = b + y_i \times \eta

η\eta是学习率,小于1。

导入库

1
2
3
4
5
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
%config InlineBackend.figure_format = 'retina'

图形示例

横坐标代表自身抽到的球的颜色,纵坐标代表上一个人声称的颜色。点的颜色是真实杯子的颜色。我们想要找到一根线,使得在这根线上面的都是红色的概率尽可能大,而下面的都是蓝色的概率尽可能大。

1
2
3
4
5
6
7
8
9
10
X = np.array([[-1,-1],[-1,0.9],[1,0.9],[-1,-0.9],[-1,1],[0.9,-0.9],[-0.9,-0.9],[-0.9,0.9],[1,-1],[0.9,0.9],[1,1],[1,0.9],[1,-0.9]])
x1,y1,x2,y2 = -1.1,1.2,1.1,-1.2
plt.scatter(X[:7, 0], X[:7, 1], color='blue',label = 'Blue cup')
plt.scatter(X[7:, 0], X[7:, 1], color='red',label = 'Red cup')
plt.plot([x1,x2], [y1,y2],'purple')
plt.xlabel('self.color')
plt.legend(loc = 'best')
plt.ylabel('fisrt_one.color')
plt.title('Example')
plt.show()

示例

仿照前文定义类

但是红球定义为1,蓝球定义为-1,同时删去Method。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Ball:
def __init__(self, color):
self.color = color

class Cup:
def __init__(self, color):
self.color = color
if color == 1:
self.balls = np.array([Ball(1), Ball(1), Ball(-1)])
else:
self.balls = np.array([Ball(1), Ball(-1), Ball(-1)])

def pick(self):
return self.balls[np.random.randint(0, 3)].color

class Cups:
def __init__(self):
self.Cups = np.array([Cup(1), Cup(-1)])

def random_pick(self):
return self.Cups[np.random.randint(0, 2)]

我们需要对人做一些改写。每次的策略实际上为

choice=sign(wx+b) choice = sign(wx + b)

xx即info和自身抽到的球的颜色。wwbb是要传递的权重和截矩。

构造函数很好理解。和上一篇一样。

strategy(self, info, weight, bias)是这样的,如果次序是1则所抽即所选。

是其他次序的话,需要用到权重和截矩。注意,这里的权重是一个二维数组,第一维记录的是给哪个次序的人的权重,第二位是真正的权重。比如对第二个人来说,他所利用到的权重就是weight[1],而weight[1]里的内容假如为[0.1,0.9],代表给第一个人的选择0.1的权重,给第二个人的选择为0.9的权重。

最后根据所选是不是真正的颜色来赋值一个布尔变量给right。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Person:
def __init__(self, order, Cup):
self.order = order
self.choice = None
self.right = False
self.cupcolor = Cup.color
self.picked = Cup.pick()

def strategy(self, info, weight, bias):
if self.order == 1:
self.choice = self.picked
else:
x = np.array(info.copy())
x = np.append(x,self.picked)
self.choice = np.matmul(weight[self.order - 1],x.T) + bias[self.order - 1]
self.choice = int(np.sign(self.choice[0]))
if self.choice == self.cupcolor:
self.right = True

生成数据

每次生成数据需要指定杯子集合,指定生成多少行数据(一行为一次测试结果),指定最大的order。因为当我们要考察第二人的选择时,我们需要第一人的选择,第二人的预测值和实际的杯子颜色。此时的order_max = 1。weight是所有人的预测权重集合的集合。bias 是误差项的集合。最后的result()返回一个dataframe。

1
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
class GenerateData:
def __init__(self, Cups, order_max, num, weight, bias):
# 生成一行的数据
for i in range(0, num):
self.cup = Cups.random_pick()
people = []
info = []
# 生成每一个人的数据
for j in range(1,order_max + 1):
people.append(Person(j, self.cup))
people[j-1].strategy(info, weight, bias)
info.append(people[j-1].choice)
info.append(Person(j,self.cup).picked)
info.append(self.cup.color)
if i == 0:
self.data = pd.DataFrame([info])
else:
self.data = self.data.append(pd.DataFrame([info]))
# 定义列名
column = []
for i in range(1, order_max + 1):
column.append("person_" + str(i)+"_choice")
column.append("person_" + str(order_max + 1) + "_picked")
column.append("truth")
self.data.columns = column

def result(self):
return self.data

测试生成数据

假设order_max为1的时候,生成测试数据。此时weight和bias不会被纳入到计算。

假设order_max为2的时候,生成测试数据。此时weight和bias的第一项会纳入到第二个人的决策当中。这里随便给个[0.4,0.6]。

1
2
3
4
5
weight = np.array([[0],[0.4,0.6],[0,0,0],[0,0,0,0],[0,0,0,0,0],[0,0,0,0,0,0]])
bias = np.array([[0],[0],[0],[0],[0],[0]])
cups = Cups()
data = GenerateData(cups, 1, 10, weight, bias).result()
data.head(10)
person_1_choice person_2_picked truth
0 1 1 -1
0 1 1 1
0 1 -1 1
0 1 1 1
0 -1 1 1
0 -1 1 -1
0 -1 1 1
0 1 -1 -1
0 -1 -1 -1
0 -1 -1 -1
1
2
data = GenerateData(cups, 2, 10, weight, bias).result()
data.head(10)

Out[7]:

person_1_choice person_2_choice person_3_picked truth
0 1 1 -1 -1
0 1 1 1 1
0 -1 -1 1 -1
0 -1 1 -1 1
0 -1 -1 -1 -1
0 1 1 -1 1
0 -1 1 -1 1
0 1 -1 1 1
0 1 1 -1 1
0 1 1 1 -1

构建模型

数据已经生成完成,接下来要开始构建一个用来训练的模型。给定data,会随机一个weight和bias。

我们设定为当两次训练的误差变动小于0.001时停止训练。

1
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
50
51
52
53
54
class Model:
def __init__(self, data):
self.data = data
self.column = data.shape[1] - 1
self.weight = np.random.rand(self.column)
self.bias = np.random.rand()
self.loss = 9999999

def train(self, learn_rate, onPrint):
# truth 是正确的杯子颜色集合
truth = np.array(self.data.iloc[:, -1])
# x 是变量的集合
x = self.data.iloc[:, :-1]
# epoch 是训练周期
epoch = 0
while (True): # 循环内为一次训练过程

# 根据权重计算预测值
prediction = np.matmul(self.weight, x.T) + self.bias

# 计算损失
loss = np.exp(- np.matmul(truth, prediction.T) / len(self.data))
# 打印损失
if onPrint:
self.print_loss(epoch)
if abs(self.loss - loss) < 0.001:
if not onPrint:
self.print_loss(epoch)
break
self.loss = loss

# 找到分类错的那些点
prediction_sign = np.sign(prediction)
self.data['prediction_sign'] = prediction_sign
mis = self.data[self.data['prediction_sign'] != self.data['truth']].copy()

# 对权重和偏差进行更新
for j in range(int(len(mis)/2)):
mis.iloc[j, :-2] = mis.iloc[j, :-2] * mis.iloc[j, -1]
self.weight = self.weight + mis.iloc[j, :-2] * learn_rate
self.bias = self.bias + mis.iloc[j, -2] * learn_rate
epoch += 1

# 打印损失和权重,用于每次训练后的返回数据
def print_loss(self,i):
weight_print = "["
for k in self.weight:
weight_print = weight_print + str(k) + " "
weight_print += ']'
print("loss = " + str(self.loss) + "; epoch = " + str(i + 1) + "; weight = " + weight_print + "; bias = " + str(self.bias))

# 返回结果的权重和偏差
def result(self):
return self.weight, self.bias

测试你的模型

先以一个两维的作为测试。

1
2
3
4
5
weight = np.array([[0], [0, 0], [0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]])
bias = np.array([[0], [0], [0], [0], [0], [0]])
data = GenerateData(cups, 1, 5000, weight, bias).result()
model = Model(data)
model.train(0.001, True)
1
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
loss = 9999999; epoch = 1; weight = [0.23987547045547986 0.21621967077143467 ]; bias = 0.2998413553519006
loss = 0.8712734204705787; epoch = 2; weight = [0.5688754704554801 0.4552196707714349 ]; bias = -0.3051586446480999
loss = 0.707664593303057; epoch = 3; weight = [0.8338754704554804 0.7622196707714352 ]; bias = 0.20384135535190054
loss = 0.5980441873867081; epoch = 4; weight = [1.1628754704554627 1.0012196707714351 ]; bias = -0.40115864464809997
loss = 0.48574269190456254; epoch = 5; weight = [1.4278754704554335 1.3082196707714013 ]; bias = 0.10784135535190047
loss = 0.41049897961292714; epoch = 6; weight = [2.27687547045534 1.029219670771432 ]; bias = 0.02284135535190039
loss = 0.3421823708965515; epoch = 7; weight = [3.1258754704552465 0.750219670771435 ]; bias = -0.06215864464809967
loss = 0.2852352399579475; epoch = 8; weight = [3.974875470455153 0.4712196707714348 ]; bias = -0.14715864464809975
loss = 0.23776544040155903; epoch = 9; weight = [4.823875470455425 0.19221967077143454 ]; bias = -0.23215864464809982
loss = 0.19819572314302897; epoch = 10; weight = [5.6728754704557085 -0.0867803292285657 ]; bias = -0.3171586446480999
loss = 0.16521133014893102; epoch = 11; weight = [6.521875470455992 -0.3657803292285659 ]; bias = -0.40215864464809997
loss = 0.13771630980090127; epoch = 12; weight = [7.370875470456276 -0.6447803292285661 ]; bias = -0.48715864464810005
loss = 0.11479710240260771; epoch = 13; weight = [8.219875470456364 -0.9237803292285663 ]; bias = -0.5721586446481001
loss = 0.09569218590802943; epoch = 14; weight = [9.068875470455893 -1.2027803292285442 ]; bias = -0.6571586446481001
loss = 0.0797667733088221; epoch = 15; weight = [9.917875470455423 -1.4817803292285134 ]; bias = -0.7421586446481002
loss = 0.06649172096681201; epoch = 16; weight = [10.766875470454952 -1.7607803292284827 ]; bias = -0.8271586446481003
loss = 0.05542594709217608; epoch = 17; weight = [11.615875470454482 -2.039780329228452 ]; bias = -0.9121586446481004
loss = 0.046201776197040495; epoch = 18; weight = [12.464875470454011 -2.3187803292284213 ]; bias = -0.9971586446481003
loss = 0.03851272257398624; epoch = 19; weight = [13.31387547045354 -2.5977803292283905 ]; bias = -1.0821586446480913
loss = 0.032103306888790976; epoch = 20; weight = [14.16287547045307 -2.87678032922836 ]; bias = -1.167158644648082
loss = 0.026760567529755003; epoch = 21; weight = [15.0118754704526 -3.155780329228329 ]; bias = -1.2521586446480726
loss = 0.022306984666574423; epoch = 22; weight = [15.860875470452129 -3.4347803292282983 ]; bias = -1.3371586446480632
loss = 0.01859458191092164; epoch = 23; weight = [16.709875470452918 -3.7137803292282676 ]; bias = -1.4221586446480539
loss = 0.015500009598336105; epoch = 24; weight = [17.558875470453955 -3.992780329228237 ]; bias = -1.5071586446480445
loss = 0.012920446326753784; epoch = 25; weight = [18.407875470454993 -4.271780329228327 ]; bias = -1.5921586446480351
loss = 0.010770182574625694; epoch = 26; weight = [19.25687547045603 -4.55078032922842 ]; bias = -1.6771586446480258
loss = 0.008977772884717182; epoch = 27; weight = [20.105875470457068 -4.829780329228513 ]; bias = -1.7621586446480164
loss = 0.007483661990972585; epoch = 28; weight = [20.954875470458106 -5.1087803292286065 ]; bias = -1.847158644648007
loss = 0.006238206013260329; epoch = 29; weight = [21.803875470459143 -5.3877803292287 ]; bias = -1.9321586446479977
loss = 0.0052000229714837505; epoch = 30; weight = [22.65287547046018 -5.666780329228793 ]; bias = -2.0171586446479886

我们尝试作图来看看最后的划分线落在哪里。如果你对上面的反复做几次,你会发现答案都不一样,但是绘制出来的直线是一样的。

1
2
3
4
5
6
7
8
9
10
11
12
weight_1,bias_1 = model.result()
X = np.array([[-1,-1],[-1,0.9],[1,0.9],[-1,-0.9],[-1,1],[0.9,-0.9],[-0.9,-0.9],[-0.9,0.9],[1,-1],[0.9,0.9],[1,1],[1,0.9],[1,-0.9]])
plt.scatter(X[:7, 0], X[:7, 1], color='blue',label = 'Blue cup')
plt.scatter(X[7:, 0], X[7:, 1], color='red',label = 'Red cup')
y1 = - ( 1.05 * weight_1[0] + bias_1) / weight_1[1]
y2 = - ( -1.05 * weight_1[0] + bias_1) / weight_1[1]
plt.plot([1.05,-1.05], [y1,y2],'purple')
plt.xlabel('self.color')
plt.legend(loc = 'best')
plt.ylabel('fisrt_one.color')
plt.title('Example')
plt.show()

图形示例

可以看得到,我们的直线还是有明显的分割数据的痕迹。Nice!

坐稳了吗?

维数要升上去了!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class MultiTest:
def __init__(self, num):
self.num = num
cups = Cups()
self.weight = np.array([[0], [0, 0], [0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]])
self.bias = np.array([[0], [0], [0], [0], [0], [0]])
for i in range(1, 7):
data = GenerateData(cups, i, num, self.weight, self.bias).result()
print("order = " + str(i))
model = Model(data)
model.train(0.001, False)
if i < 6:
self.weight[i],self.bias[i] = model.result()
self.data = GenerateData(cups, 6, num, self.weight, self.bias).result()

def result(self):
return self.data.drop(['person_7_picked'],axis=1)

Attention! 开始训练!

1
2
num = 5000
data = MultiTest(num)
1
2
3
4
5
6
7
8
9
10
11
12
order = 1
loss = 0.005632615600654567; epoch = 28; weight = [-6.9400970623982206 23.644636498581562 ]; bias = 1.0439756387293708
order = 2
loss = 0.012129428414900171; epoch = 56; weight = [-14.95722278061538 46.088491656124184 -16.13441487947676 ]; bias = -1.2900657211884876
order = 3
loss = 0.0024075511379628317; epoch = 18; weight = [-2.8635679182045157 14.06077748591194 13.782396723647578 -4.9053285240665785 ]; bias = -0.33361307832754755
order = 4
loss = 0.0013965637022787863; epoch = 12; weight = [-2.1373722732520526 8.447166586885972 9.105230463980694 8.751180879482295 -2.090788136470222 ]; bias = -0.7294047529291638
order = 5
loss = 0.000868608869866401; epoch = 9; weight = [-1.8382259255099622 7.022554425066799 7.611470890947788 6.811853615522083 7.325222400362925 -2.170812655689555 ]; bias = 0.15422166071888854
order = 6
loss = 0.0008766155301412153; epoch = 7; weight = [-0.7047889477000489 6.1073293159417625 5.413637735955178 5.508093914343887 5.208391688772499 5.932105114322674 -0.9061096280532147 ]; bias = 0.43218576276939324

查看训练结果

1
2
result = data.result()
result.head()
person_1_choice person_2_choice person_3_choice person_4_choice person_5_choice person_6_choice truth
0 1 1 1 1 1 1 -1
0 1 1 1 1 1 1 -1
0 1 1 1 1 1 1 1
0 1 1 1 1 1 1 1
0 1 1 1 1 1 1 -1

让我们使用老方法来完整地统计并绘图。

1
2
3
4
5
6
7
8
ratio = []
for i in range(1,7):
string = 'person_' + str(i) + "_choice"
temp = (len(result[result[string] == result['truth']]))/num
ratio.append(temp)
ratio = pd.DataFrame(ratio,index = [1,2,3,4,5,6])
ratio.columns = ['correct ratio']
ratio
correct ratio
1 0.6704
2 0.6616
3 0.6616
4 0.6616
5 0.6616
6 0.6616
1
ratio.plot(kind='bar', ylim=(0, 1), figsize=(13, 6))

最终结果

这就是神经网络

它并不比我们推导出来的最优解更好。

这是因为,我们的数据是线性不可分的,天然不是一个适合用神经网络的数据集。

但这并不妨碍我们来尝试使用神经网络。