最近有些朋友总来问我有关遗传算法的东西,我是在大学搞数学建模的时候接触过一些最优化和进化算法方面的东西,之前也写过几篇博客记录过,好比遗传算法的C语言实现(一):以非线性函数求极值为例和C语言实现粒子群算法(PSO)一等,若是对原理有兴趣的话能够去个人博客具体查看:Lyrichu's Blog。因此突发奇想,干脆把之前写的一些进化算法好比遗传算法(GA),粒子群算法(PSO),模拟退火算法(SA)以及最近看的基于梯度的一些优化算法好比Gradient Descent,SGD,Momentum等整理一下,写成一个python库,方便那些有须要的朋友使用。断断续续花了几天的时间,初步完成了一些基本功能,这里简单介绍一下。
html
pip install sopt
便可。项目的github地址是
sopt。目前sopt包含的优化方法以下:
from sopt.SGA import SGA from math import sin def func1(x): return (x[0]-1)**2 + (sin(x[1])-0.5)**4 + 2 if __name__ == '__main__': sga = SGA.SGA(func = func1,func_type = 'min',variables_num = 2, lower_bound = 0,upper_bound = 2,generations = 20, binary_code_length = 10) # run SGA sga.run() # show the SGA optimization result in figure sga.save_plot() # print the result sga.show_result()
运行结果以下:python
-------------------- SGA config is: -------------------- lower_bound:[0, 0] generations:20 cross_rate:0.7 variables_num:2 mutation_rate:0.1 func_type:min upper_bound:[2, 2] population_size:100 func:<function func1 at 0x7f3d2311b158> binary_code_length:10 -------------------- SGA caculation result is: -------------------- global best generation index/total generations:3/20 global best point:[1.00488759 0.45356794] global best target:2.00003849823336
用图像展现为图1所示:
linux
variables_num
的ndarray,表示每一个变量的下界,若是是一个数字的话,咱们认为全部的下界都是同样的(必填);variables_num
的ndarray,表示每一个变量的上界,若是是一个数字的话,咱们认为全部的上界都是同样的(必填);from sopt.GA.GA import GA from sopt.util.functions import * from sopt.util.ga_config import * from sopt.util.constraints import * class TestGA: def __init__(self): self.func = quadratic11 self.func_type = quadratic11_func_type self.variables_num = quadratic11_variables_num self.lower_bound = quadratic11_lower_bound self.upper_bound = quadratic11_upper_bound self.cross_rate = 0.8 self.mutation_rate = 0.05 self.generations = 200 self.population_size = 100 self.binary_code_length = 20 self.cross_rate_exp = 1 self.mutation_rate_exp = 1 self.code_type = code_type.binary self.cross_code = False self.select_method = select_method.proportion self.rank_select_probs = None self.tournament_num = 2 self.cross_method = cross_method.uniform self.arithmetic_cross_alpha = 0.1 self.arithmetic_cross_exp = 1 self.mutation_method = mutation_method.uniform self.none_uniform_mutation_rate = 1 #self.complex_constraints = [constraints1,constraints2,constraints3] self.complex_constraints = None self.complex_constraints_method = complex_constraints_method.penalty self.complex_constraints_C = 1e6 self.M = 1e8 self.GA = GA(**self.__dict__) def test(self): start_time = time() self.GA.run() print("GA costs %.4f seconds!" % (time()-start_time)) self.GA.save_plot() self.GA.show_result() if __name__ == '__main__': TestGA().test()
上面代码的运行结果为:git
GA costs 6.8320 seconds! -------------------- GA config is: -------------------- func:<function quadratic11 at 0x7f998927bd08> code_type:binary complex_constraints:None global_generations_step:200 cross_method:uniform mutation_method:uniform cross_rate:0.8 lower_bound:[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] tournament_num:2 variables_num:11 complex_constraints_method:penalty none_uniform_mutation_rate:1 population_size:100 mutation_rate:0.05 generations:200 arithmetic_cross_alpha:0.1 func_type:min mutation_rate_exp:1 cross_rate_exp:1 arithmetic_cross_exp:1 M:100000000.0 select_method:proportion upper_bound:[11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11] cross_code:False binary_code_length:20 complex_constraints_C:1000000.0 -------------------- GA caculation result is: -------------------- global best target generation index/total generations:149/200 global best point:[ 1.07431037 2.41401426 2.4807906 4.36291634 4.90653029 6.13753427 6.58147963 7.7370479 9.42957347 10.46616122 10.87151134] global best target:2.2685208668743204
其中sopt.util.functions
预约义了一些测试函数,quadratic11
是一个11元变量的二次函数,函数原型为:\(quadratic11(x_1,...,x_{11})=(x_1-1)^2 +(x_2-2)^2 + ... +(x_{11}-11)^2 + 1\),其中\(1 \le x_1,...,x_{11} \le 11\),函数的最小值点在(1,2,...,11)处取得,最小值为1。另外还定义了其余几个测试函数为:github
xx_config
的模块,其中定义了该优化方法的一些经常使用默认参数,好比ga_config
中就定义了一些GA的一些经常使用优化参数,ga_config.basic_config
定义了一些基础参数设置,好比basic_config.generations
是一个默认进化代数,basic_config.mutation_rate
是默认的变异参数;而ga_config.cross_method
则预约义了全部支持的交叉方法,好比cross_method.uniform
表示均匀交叉,cross_method.one_point
表示单点交叉等;ga_config.mutation_method
等也是相似的。有了这些预约义变量,能够免去咱们手动输入不少参数取值以及传入方法字符串的麻烦(有时候可能会写错)。GA costs 1.7245 seconds! -------------------- GA config is: -------------------- population_size:100 lower_bound:[-2.048, -2.048] mutation_rate_exp:1 select_method:proportion code_type:binary global_generations_step:200 generations:200 mutation_method:uniform complex_constraints_method:penalty binary_code_length:20 cross_method:uniform arithmetic_cross_alpha:0.1 func:<function Rosenbrock at 0x7fe5fd538a60> upper_bound:[2.048, 2.048] cross_code:False mutation_rate:0.05 cross_rate_exp:1 complex_constraints_C:1000000.0 cross_rate:0.8 variables_num:2 M:100000000.0 complex_constraints:None none_uniform_mutation_rate:1 tournament_num:2 arithmetic_cross_exp:1 func_type:max -------------------- GA caculation result is: -------------------- global best target generation index/total generations:75/200 global best point:[-2.04776953 -2.04537109] global best target:3901.4655271502425
图2是每代最优值的计算结果:
算法
generations
,
population_size
,
func_type
等。和SGA同样的参数这里就再也不列举了,以下是GA特有的一些参数:
cross_rate_exp
,默认取值为1,通常设置为一个比1稍大的数字,好比1.0001等;cross_rate_exp
相似;population_size
的ndarray,全部元素按照递增排序,数组和为1;arithmetic_cross_alpha
,其默认值为0.1;arithmetic_cross_exp
,默认取值为1,通常取一个比1稍大的数,好比1.0001,t是进化代数;none_uniform
中定义的系统参数\(b\);def func1(x): x1 = x[0] x2 = x[1] return x1**2 + x2**2 - 3
penalty
即惩罚函数法,暂时不支持其余的求解方式;penalty
求解复杂约束的系数\(C\),好比对于某一个约束\(x_1^2+x_2^2 < 3\),GA在求解过程当中,违反了该约束,即解知足\(x_1^2+x_2^2 \ge 3\),那么咱们对目标函数增长一个惩罚项: \(C(x_1^2+x_2^2-3)\),\(C\)通常取一个很大的正数,默认值为\(10^6\)。from time import time from sopt.util.functions import * from sopt.util.pso_config import * from sopt.PSO.PSO import PSO from sopt.util.constraints import * class TestPSO: def __init__(self): self.func = quadratic11 self.func_type = quadratic11_func_type self.variables_num = quadratic11_variables_num self.lower_bound = quadratic11_lower_bound self.upper_bound = quadratic11_upper_bound self.c1 = basic_config.c1 self.c2 = basic_config.c2 self.generations = 200 self.population_size = 100 self.vmax = 1 self.vmin = -1 self.w = 1 self.w_start = 0.9 self.w_end = 0.4 self.w_method = pso_w_method.linear_decrease #self.complex_constraints = [constraints1,constraints2,constraints3] self.complex_constraints = None self.complex_constraints_method = complex_constraints_method.loop self.PSO = PSO(**self.__dict__) def test(self): start_time = time() self.PSO.run() print("PSO costs %.4f seconds!" %(time()-start_time)) self.PSO.save_plot() self.PSO.show_result() if __name__ == '__main__': TestPSO().test()
运行结果为:windows
PSO costs 1.1731 seconds! -------------------- PSO config is: -------------------- complex_constraints_method:loop c1:1.49445 lower_bound:[1 1 1 1 1 1 1 1 1 1 1] w_end:0.4 w_method:linear_decrease complex_constraints:None func:<function quadratic11 at 0x7f1ddb81a510> upper_bound:[11 11 11 11 11 11 11 11 11 11 11] generations:200 func_type:min w:1 c2:1.49445 w_start:0.9 population_size:100 vmin:-1 vmax:1 variables_num:11 -------------------- PSO calculation result is: -------------------- global best generation index/total generations: 198/200 global best point: [ 1. 1.99999999 2.99999999 4. 5. 6. 7.00000001 7.99999999 9.00000001 10.00000001 11. ] global best target: 1.0
上面的代码意图应该是很是明显的,目标函数是\(quadratic11\),最终求得的最小值点几乎就是全局最小值点。下面是PSO类中全部参数的具体定义:数组
variables_num
的ndarray,表示每一个变量的下界,若是是一个数字的话,咱们认为全部的下界都是同样的(必填);variables_num
的ndarray,表示每一个变量的上界,若是是一个数字的话,咱们认为全部的上界都是同样的(必填);from time import time from sopt.util.functions import * from sopt.Optimizers.SA import SA from sopt.util.sa_config import * from sopt.util.constraints import * class TestSA: def __init__(self): self.func = Rosenbrock self.func_type = Rosenbrock_func_type self.variables_num = Rosenbrock_variables_num self.lower_bound = Rosenbrock_lower_bound self.upper_bound = Rosenbrock_upper_bound self.T_start = 100 self.T_end = 1e-6 self.q = 0.9 self.L = 100 self.init_pos = None #self.complex_constraints = [constraints1,constraints2,constraints3] self.complex_constraints_method = complex_constraints_method.loop self.SA = SA(**self.__dict__) def test(self): start_time = time() self.SA.run() print("SA costs %.4f seconds!" %(time()-start_time)) self.SA.save_plot() self.SA.show_result() if __name__ == '__main__': TestSA().test()
运行结果以下:dom
SA costs 0.2039 seconds! -------------------- SA config is: -------------------- func_type:max complex_constraints:None q:0.9 complex_constraints_method:loop T_start:100 steps:17500 T_end:1e-06 L:100 func:<function Rosenbrock at 0x7f8261799048> variables_num:2 lower_bound:[-2.048 -2.048] init_pos:[-2.03887265 -2.02503927] upper_bound:[2.048 2.048] -------------------- SA calculation result is: -------------------- global best generation index/total generations:2126/17500 global best point: [-2.03887265 -2.02503927] global best target: 3830.997799328349
SA类的具体参数含义以下:函数
variables_num
的ndarray,表示每一个变量的下界,若是是一个数字的话,咱们认为全部的下界都是同样的(必填);variables_num
的ndarray,表示每一个变量的上界,若是是一个数字的话,咱们认为全部的上界都是同样的(必填);from time import time from sopt.util.functions import * from sopt.util.constraints import * from sopt.util.random_walk_config import * from sopt.Optimizers.RandomWalk import RandomWalk class TestRandomWalk: def __init__(self): self.func = quadratic50 self.func_type = quadratic50_func_type self.variables_num = quadratic50_variables_num self.lower_bound = quadratic50_lower_bound self.upper_bound = quadratic50_upper_bound self.generations = 100 self.init_step = 10 self.eps = 1e-4 self.vectors_num = 10 self.init_pos = None # self.complex_constraints = [constraints1,constraints2,constraints3] self.complex_constraints = None self.complex_constraints_method = complex_constraints_method.loop self.RandomWalk = RandomWalk(**self.__dict__) def test(self): start_time = time() self.RandomWalk.random_walk() print("random walk costs %.4f seconds!" %(time() - start_time)) self.RandomWalk.save_plot() self.RandomWalk.show_result() if __name__ == '__main__': TestRandomWalk().test()
运行结果为:
Finish 1 random walk! Finish 2 random walk! Finish 3 random walk! Finish 4 random walk! Finish 5 random walk! Finish 6 random walk! Finish 7 random walk! Finish 8 random walk! Finish 9 random walk! Finish 10 random walk! Finish 11 random walk! Finish 12 random walk! Finish 13 random walk! Finish 14 random walk! Finish 15 random walk! Finish 16 random walk! Finish 17 random walk! random walk costs 1.0647 seconds! -------------------- random walk config is: -------------------- init_step:10 eps:0.0001 generations_nums:9042 lower_bound:[1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1] complex_constraints_method:loop walk_nums:17 complex_constraints:None vectors_num:10 upper_bound:[50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50] variables_num:50 func:<function quadratic50 at 0x7f51555a1840> generations:100 func_type:min -------------------- random walk caculation result is: -------------------- global best generation index/total generations:8942/9042 global best point is: [ 1.00004803 1.99999419 3.00000569 3.99998558 5.00002455 5.99999255 6.99992476 7.99992864 9.00000401 9.99994717 10.99998155 12.00002429 13.0000035 13.99998567 15.00000421 16.00001454 16.99997252 17.99998041 19.00002491 20.00003141 21.00004182 21.99998565 22.99997668 23.99999821 24.99995881 25.99999359 27.00000443 28.00005117 28.99998132 30.00004136 31.00002021 32.00000616 33.00000678 34.00005423 35.00001799 36.00000051 37.00002749 38.00000203 39.00007087 39.9999964 41.00004432 42.0000158 42.99992991 43.99995352 44.99997267 46.00003533 46.9999834 47.99996778 49.00002904 50. ] global best target is: 1.0000000528527013
通过实验发现,Random Walk 具备很是强的全局寻优能力,对于quadratic50这种具备50个变量的复杂目标函数,它也能够很快找到其全局最优势,并且运行速度也很快。RandomWalk类的具体参数含义以下:
variables_num
的ndarray,表示每一个变量的下界,若是是一个数字的话,咱们认为全部的下界都是同样的(必填);variables_num
的ndarray,表示每一个变量的上界,若是是一个数字的话,咱们认为全部的上界都是同样的(必填);from time import time from sopt.GA.GA import GA from sopt.util.functions import * from sopt.util.ga_config import * from sopt.util.constraints import * class TestGA: def __init__(self): self.func = Rosenbrock self.func_type = Rosenbrock_func_type self.variables_num = Rosenbrock_variables_num self.lower_bound = Rosenbrock_lower_bound self.upper_bound = Rosenbrock_upper_bound self.cross_rate = 0.8 self.mutation_rate = 0.1 self.generations = 300 self.population_size = 200 self.binary_code_length = 20 self.cross_rate_exp = 1 self.mutation_rate_exp = 1 self.code_type = code_type.real self.cross_code = False self.select_method = select_method.proportion self.rank_select_probs = None self.tournament_num = 2 self.cross_method = cross_method.uniform self.arithmetic_cross_alpha = 0.1 self.arithmetic_cross_exp = 1 self.mutation_method = mutation_method.uniform self.none_uniform_mutation_rate = 1 self.complex_constraints = [constraints1,constraints2,constraints3] self.complex_constraints_method = complex_constraints_method.penalty self.complex_constraints_C = 1e8 self.M = 1e8 self.GA = GA(**self.__dict__) def test(self): start_time = time() self.GA.run() print("GA costs %.4f seconds!" % (time()-start_time)) self.GA.save_plot() self.GA.show_result() if __name__ == '__main__': TestGA().test()
运行结果以下:
GA costs 1.9957 seconds! -------------------- GA config is: -------------------- lower_bound:[-2.048, -2.048] cross_code:False complex_constraints_method:penalty mutation_method:uniform mutation_rate:0.1 mutation_rate_exp:1 cross_rate:0.8 upper_bound:[2.048, 2.048] arithmetic_cross_exp:1 variables_num:2 generations:300 tournament_num:2 select_method:proportion func_type:max complex_constraints_C:100000000.0 cross_method:uniform complex_constraints:[<function constraints1 at 0x7f5efe2e8d08>, <function constraints2 at 0x7f5efe2e8d90>, <function constraints3 at 0x7f5efe2e8e18>] func:<function Rosenbrock at 0x7f5efe2e87b8> none_uniform_mutation_rate:1 cross_rate_exp:1 code_type:real M:100000000.0 binary_code_length:20 global_generations_step:300 population_size:200 arithmetic_cross_alpha:0.1 -------------------- GA caculation result is: -------------------- global best target generation index/total generations:226/300 global best point:[ 1.7182846 -1.74504313] global best target:2207.2089435117955
上面的constraints1,constraints2,constraints3是三个预约义的约束条件函数,其定义分别为:\(constraints1:x_1^2 + x_2^2 - 6 \le 0\);\(constraints2:x_1 + x_2 \le 0\);\(constraints3:-2-x_1 - x_2 \le 0\),函数原型为:
def constraints1(x): x1 = x[0] x2 = x[1] return x1**2 + x2**2 -3 def constraints2(x): x1 = x[0] x2 = x[1] return x1+x2 def constraints3(x): x1 = x[0] x2 = x[1] return -2 -x1 -x2
其实观察能够发现,上面的代码和原始的GA实例代码惟一的区别,就是其增长了self.complex_constraints = [constraints1,constraints2,constraints3]
这样一句,对于其余的优化方法,其都定义了complex_constraints
和complex_constraints_method
这两个属性,只要传入相应的约束条件函数列表以及求解约束条件的方法就能够求解带复杂约束的目标函数了。好比咱们再用Random Walk求解和上面同样的带三个约束的Rosenbrock函数,代码及运行结果以下:
from time import time from sopt.util.functions import * from sopt.util.constraints import * from sopt.util.random_walk_config import * from sopt.Optimizers.RandomWalk import RandomWalk class TestRandomWalk: def __init__(self): self.func = Rosenbrock self.func_type = Rosenbrock_func_type self.variables_num = Rosenbrock_variables_num self.lower_bound = Rosenbrock_lower_bound self.upper_bound = Rosenbrock_upper_bound self.generations = 100 self.init_step = 10 self.eps = 1e-4 self.vectors_num = 10 self.init_pos = None self.complex_constraints = [constraints1,constraints2,constraints3] self.complex_constraints_method = complex_constraints_method.loop self.RandomWalk = RandomWalk(**self.__dict__) def test(self): start_time = time() self.RandomWalk.random_walk() print("random walk costs %.4f seconds!" %(time() - start_time)) self.RandomWalk.save_plot() self.RandomWalk.show_result() if __name__ == '__main__': TestRandomWalk().test()
运行结果:
Finish 1 random walk! Finish 2 random walk! Finish 3 random walk! Finish 4 random walk! Finish 5 random walk! Finish 6 random walk! Finish 7 random walk! Finish 8 random walk! Finish 9 random walk! Finish 10 random walk! Finish 11 random walk! Finish 12 random walk! Finish 13 random walk! Finish 14 random walk! Finish 15 random walk! Finish 16 random walk! Finish 17 random walk! random walk costs 0.1543 seconds! -------------------- random walk config is: -------------------- eps:0.0001 func_type:max lower_bound:[-2.048 -2.048] upper_bound:[2.048 2.048] init_step:10 vectors_num:10 func:<function Rosenbrock at 0x7f547fc952f0> variables_num:2 walk_nums:17 complex_constraints_method:loop generations:100 generations_nums:2191 complex_constraints:[<function constraints1 at 0x7f547fc95bf8>, <function constraints2 at 0x7f547fc95c80>, <function constraints3 at 0x7f547fc95d08>] -------------------- random walk caculation result is: -------------------- global best generation index/total generations:2091/2191 global best point is: [-2.41416736 0.41430367] global best target is: 2942.6882849234585
能够发现Random Walk 求解获得的最优解要比GA好,并且运行时间更快,通过实验发现,在全部的优化方法中,不管是求解带复杂约束仍是不带复杂约束条件的目标函数,求解效果大致上排序是:Random Walk > PSO > GA > SA 。因此当你在求解具体问题时,不妨多试几种优化方法,而后择优选择。
from time import time from sopt.util.gradients_config import * from sopt.util.functions import * from sopt.Optimizers.Gradients import GradientDescent class TestGradientDescent: def __init__(self): self.func = quadratic50 self.func_type = quadratic50_func_type self.variables_num = quadratic50_variables_num self.init_variables = None self.lr = 1e-3 self.epochs = 5000 self.GradientDescent = GradientDescent(**self.__dict__) def test(self): start_time = time() self.GradientDescent.run() print("Gradient Descent costs %.4f seconds!" %(time()-start_time)) self.GradientDescent.save_plot() self.GradientDescent.show_result() if __name__ == '__main__': TestGradientDescent().test()
运行结果为:
Gradient Descent costs 14.3231 seconds! -------------------- Gradient Descent config is: -------------------- func_type:min variables_num:50 func:<function quadratic50 at 0x7f74e737b620> epochs:5000 lr:0.001 -------------------- Gradient Descent caculation result is: -------------------- global best epoch/total epochs:4999/5000 global best point: [ 0.9999524 1.99991045 2.99984898 3.9998496 4.99977767 5.9997246 6.99967516 7.99964102 8.99958143 9.99951782 10.99947879 11.99944665 12.99942492 13.99935192 14.99932708 15.99925856 16.99923686 17.99921689 18.99911527 19.9991255 20.99908968 21.99899699 22.99899622 23.99887832 24.99883597 25.99885616 26.99881394 27.99869772 28.99869349 29.9986766 30.99861142 31.99851987 32.998556 33.99849351 34.99845985 35.99836731 36.99832444 37.99831792 38.99821067 39.99816567 40.99814951 41.99808199 42.99808161 43.99806655 44.99801207 45.99794449 46.99788003 47.99785468 48.99780825 49.99771656] global best target: 1.0000867498727912
下面简要说明如下GradientDescent,Momentum等类中的主要参数,像func
,variables_num
等含义已经解释不少次了,再也不赘述,这里主要介绍各种特有的一些参数。
from sopt.test import *
导入预约义的示例测试,来运行观察结果。好比下面的代码就运行了PSO的一个示例测试:from sopt.test import test_PSO test_PSO.TestPSO().test()
结果:
PSO costs 3.4806 seconds! -------------------- PSO config is: -------------------- lower_bound:[1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1] generations:200 vmin:-1 func:<function quadratic50 at 0x7fd3bf37d8c8> w_method:linear_decrease func_type:min population_size:100 w_start:0.9 complex_constraints_method:loop vmax:1 c2:1.49445 upper_bound:[50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50 50] variables_num:50 c1:1.49445 w:1 complex_constraints:None w_end:0.4 -------------------- PSO calculation result is: -------------------- global best generation index/total generations: 199/200 global best point: [ 1. 1. 2.94484328 4.13875216 5.00293498 6.13124759 6.99713025 7.92116383 8.87648843 10.02066994 11.0758768 12.02240279 13.01125368 13.98010373 14.98063168 15.97776149 17.11878537 18.00246112 18.14780887 20.00637617 21.00223704 22.00689373 23.14823218 24.0002456 24.98672157 25.99141686 27.02112321 28.01540506 29.05403155 30.07304888 31.00414822 32.00982867 32.99444884 33.9114213 34.96631157 36.22871824 37.0015616 37.98907918 39.01245751 40.1371835 41.0182043 42.07768102 42.87178292 43.93687997 45.05786395 46.03778693 47.07913415 50. 48.9964866 50. ] global best target: 6.95906097685
附录会简要说明上文提到的一些概念,待有空更新。。。