对于数学建模里用到的多目标优化算法进行探究
使用上次建模选拔赛里做的匆忙且不完整的调参作为优化问题
选择对下面的方程进行拟合确定参数
选择 [1:100] 的100个散点作为拟合区间

  1. def realFuction(x):
  2. return -0.02*x**2 +2*x+5*math.sin(x)
  3. def myFuc(a1,a2,a3,x):
  4. return a1*x**2 + a2*x + a3*math.sin(x)

模拟退火

基于参考链接的代码
但是源代码我认为有一些问题:第57行原代码为rp,y和yNew相近的时候我认为逼近优解,所以应该用小概率接受较差解,所以改了。感觉效果也有提升

相对于源代码,我加上了保存最优解的代码,在后面的结果展示里也可以看到对应的结果,防止在最后的遍历里小概率的选择了较差解,导致结果极差

模拟退火参数选择:

  1. 对于T0决定了一开始参数的变化幅度(参数变化乘了T),所以不易选择过大(容易震荡)或过小
  2. T0和Tmin又共同决定了温度变化的幅度,影响了参数的变化幅度和退火过程长度
  3. T = T0 / math.log2(1 + t)决定温度变化速度,不加log更快,但是结果不一定更好
  4. 对于k即每一个温度下对参数的尝试调整次数 ```python from future import division import numpy as np import matplotlib.pyplot as plt import math from Draw import Draw def realFuction(x): return -0.02x**2 +2x+5*math.sin(x)

def myFuc(a1,a2,a3,x): return a1x**2 + a2x + a3*math.sin(x)

def aimFunction(para,x): res=0 for i in x: res += math.sqrt((myFuc(para[0],para[1],para[2],i)-realFuction(i))**2) return res/len(x)

x = [i for i in range(100)] y = [0 for i in range(100)] for i in range(100): y[i] = realFuction(x[i])

T0 = 200 # initiate temperature\ T=T0 Tmin = 15 # minimum value of terperature

para=[np.random.uniform(low=-1, high=5) for _ in range(3)]

k = 100 # times of internal circulation temp_y = 0 # initiate result t = 0 # time best=[] best_fit=0 while T >= Tmin: for i in range(k):

  1. # calculate y
  2. temp_y = aimFunction(para,x)
  3. # generate a new x in the neighboorhood of x by transform function
  4. paraNew = [i+np.random.uniform(low=-0.0005,high=0.0005)*T for i in para]
  5. yNew = aimFunction(paraNew,x)
  6. if len(best)==0:
  7. best_fit=yNew
  8. best=paraNew
  9. else:
  10. if yNew<best_fit:
  11. best=paraNew
  12. best_fit=yNew
  13. if yNew - temp_y < 0:
  14. para = paraNew
  15. else:
  16. # metropolis principle
  17. p = math.exp(-(yNew - temp_y) / T)
  18. r = np.random.uniform(low=0, high=1)
  19. if r > p:
  20. para = paraNew
  21. print(para,aimFunction(para,x),best_fit)
  22. t += 1
  23. T = T0 / math.log2(1 + t)
  24. print("cur T: ",T)

result=[] bestresult=[] for i in x: result.append(myFuc(para[0],para[1],para[2],i)) bestresult.append(myFuc(best[0],best[1],best[2],i)) print(para) print(best) Draw([y,result,bestresult],[“real”,”predict”,”best”],x,”Real and predict”,Colors=None)

  1. (效果比较好的图忘记截图参数结果了)<br />![Figure_1.png](https://cdn.nlark.com/yuque/0/2020/png/296244/1598551674668-4babb2e7-5f61-4314-b47a-9444d1112fd2.png#align=left&display=inline&height=700&margin=%5Bobject%20Object%5D&name=Figure_1.png&originHeight=700&originWidth=1200&size=77340&status=done&style=none&width=1200)<br />(重新跑了,调了一些参数的结果,Best的拟合效果巨好,但是perdict飘了,可以看截图里的最后一次退火计算,不知道为啥第一个参数突变了,但是Best效果实在太好了)<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/296244/1598551698610-1691f3fd-a0e5-4724-b710-7542441004f5.png#align=left&display=inline&height=408&margin=%5Bobject%20Object%5D&name=image.png&originHeight=817&originWidth=1341&size=114057&status=done&style=none&width=670.5)<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/296244/1598551711489-74d375c9-7bd8-4699-afb9-dbfe2faba449.png#align=left&display=inline&height=142&margin=%5Bobject%20Object%5D&name=image.png&originHeight=284&originWidth=1090&size=38053&status=done&style=none&width=545)
  2. <a name="j8Uj0"></a>
  3. # 遗传算法
  4. 基于参考资料做了一下修改<br />构造了问题实例和损失函数,同时修改了突变的操作<br />(原来的是在范围里直接随机一个,实在感觉有点随便,对于解空间比较大的参数可能不适合?)<br />最后修改了Fitness的计算<br />原先是fit值向大的方向优化,这里是需要缩小gap<br />第一次改为负号,不可以,会影响到选择的时候(基于Fitness占比选择遗传,负数导致错误)<br />第二次改为倒数
  5. ```python
  6. import random
  7. import math
  8. import numpy as np
  9. from operator import itemgetter
  10. def realFuction(x):
  11. return -0.02*x**2 +2*x+5*math.sin(x)
  12. def myFuc(a1,a2,a3,x):
  13. return a1*x**2 + a2*x + a3*math.sin(x)
  14. def aimFunction(para,x):
  15. res=0
  16. for i in x:
  17. res += math.sqrt((myFuc(para[0],para[1],para[2],i)-realFuction(i))**2)
  18. return 1/(res/len(x))
  19. x = [i for i in range(100)]
  20. y = [0 for i in range(100)]
  21. for i in range(100):
  22. y[i] = realFuction(x[i])
  23. class Gene:
  24. """
  25. This is a class to represent individual(Gene) in GA algorithom
  26. each object of this class have two attribute: data, size
  27. """
  28. def __init__(self, **data):
  29. self.__dict__.update(data)
  30. self.size = len(data['data']) # length of gene
  31. class GA:
  32. """
  33. This is a class of GA algorithm.
  34. """
  35. def __init__(self, parameter):
  36. """
  37. Initialize the pop of GA algorithom and evaluate the pop by computing its' fitness value.
  38. The data structure of pop is composed of several individuals which has the form like that:
  39. {'Gene':a object of class Gene, 'fitness': 1.02(for example)}
  40. Representation of Gene is a list: [b s0 u0 sita0 s1 u1 sita1 s2 u2 sita2]
  41. """
  42. # parameter = [CXPB, MUTPB, NGEN, popsize, low, up]
  43. self.parameter = parameter
  44. low = self.parameter[4]
  45. up = self.parameter[5]
  46. self.bound = []
  47. self.bound.append(low)
  48. self.bound.append(up)
  49. pop = []
  50. for i in range(self.parameter[3]):
  51. geneinfo = []
  52. for pos in range(len(low)):
  53. geneinfo.append(random.randint(self.bound[0][pos], self.bound[1][pos])) # initialise popluation
  54. fitness = self.evaluate(geneinfo) # evaluate each chromosome
  55. pop.append({'Gene': Gene(data=geneinfo), 'fitness': fitness}) # store the chromosome and its fitness
  56. self.pop = pop
  57. self.bestindividual = self.selectBest(self.pop) # store the best chromosome in the population
  58. def evaluate(self, geneinfo):
  59. """
  60. fitness function
  61. """
  62. y = aimFunction(geneinfo,x)
  63. return y
  64. def selectBest(self, pop):
  65. """
  66. select the best individual from pop
  67. """
  68. s_inds = sorted(pop, key=itemgetter("fitness"), reverse=True) # from large to small, return a pop
  69. return s_inds[0]
  70. def selection(self, individuals, k):
  71. """
  72. select some good individuals from pop, note that good individuals have greater probability to be choosen
  73. for example: a fitness list like that:[5, 4, 3, 2, 1], sum is 15,
  74. [-----|----|---|--|-]
  75. 012345|6789|101112|1314|15
  76. we randomly choose a value in [0, 15],
  77. it belongs to first scale with greatest probability
  78. """
  79. s_inds = sorted(individuals, key=itemgetter("fitness"),
  80. reverse=True) # sort the pop by the reference of fitness
  81. sum_fits = sum(ind['fitness'] for ind in individuals) # sum up the fitness of the whole pop
  82. chosen = []
  83. for i in range(k):
  84. u = random.random() * sum_fits # randomly produce a num in the range of [0, sum_fits], as threshold
  85. sum_ = 0
  86. for ind in s_inds:
  87. sum_ += ind['fitness'] # sum up the fitness
  88. if sum_ >= u:
  89. # when the sum of fitness is bigger than u, choose the one, which means u is in the range of
  90. # [sum(1,2,...,n-1),sum(1,2,...,n)] and is time to choose the one ,namely n-th individual in the pop
  91. chosen.append(ind)
  92. break
  93. # from small to large, due to list.pop() method get the last element
  94. chosen = sorted(chosen, key=itemgetter("fitness"), reverse=False)
  95. return chosen
  96. def crossoperate(self, offspring):
  97. """
  98. cross operation
  99. here we use two points crossoperate
  100. for example: gene1: [5, 2, 4, 7], gene2: [3, 6, 9, 2], if pos1=1, pos2=2
  101. 5 | 2 | 4 7
  102. 3 | 6 | 9 2
  103. =
  104. 3 | 2 | 9 2
  105. 5 | 6 | 4 7
  106. """
  107. dim = len(offspring[0]['Gene'].data)
  108. geninfo1 = offspring[0]['Gene'].data # Gene's data of first offspring chosen from the selected pop
  109. geninfo2 = offspring[1]['Gene'].data # Gene's data of second offspring chosen from the selected pop
  110. if dim == 1:
  111. pos1 = 1
  112. pos2 = 1
  113. else:
  114. pos1 = random.randrange(1, dim) # select a position in the range from 0 to dim-1,
  115. pos2 = random.randrange(1, dim)
  116. newoff1 = Gene(data=[]) # offspring1 produced by cross operation
  117. newoff2 = Gene(data=[]) # offspring2 produced by cross operation
  118. temp1 = []
  119. temp2 = []
  120. for i in range(dim):
  121. if min(pos1, pos2) <= i < max(pos1, pos2):
  122. temp2.append(geninfo2[i])
  123. temp1.append(geninfo1[i])
  124. else:
  125. temp2.append(geninfo1[i])
  126. temp1.append(geninfo2[i])
  127. newoff1.data = temp1
  128. newoff2.data = temp2
  129. return newoff1, newoff2
  130. def mutation(self, crossoff, bound):
  131. """
  132. mutation operation
  133. """
  134. '''
  135. dim = len(crossoff.data)
  136. if dim == 1:
  137. pos = 0
  138. else:
  139. pos = random.randrange(0, dim) # chose a position in crossoff to perform mutation.
  140. #
  141. crossoff.data[pos] = random.uniform(bound[0][pos], bound[1][pos])
  142. '''
  143. crossoff.data = [i + np.random.uniform(low=-0.001, high=0.001) for i in crossoff.data]
  144. return crossoff
  145. def GA_main(self):
  146. """
  147. main frame work of GA
  148. """
  149. popsize = self.parameter[3]
  150. print("Start of evolution")
  151. # Begin the evolution
  152. for g in range(NGEN):
  153. print("------ Generation {} ------".format(g))
  154. # Apply selection based on their converted fitness
  155. selectpop = self.selection(self.pop, popsize)
  156. nextoff = []
  157. while len(nextoff) != popsize:
  158. # Apply crossover and mutation on the offspring
  159. # Select two individuals
  160. offspring = [selectpop.pop() for _ in range(2)]
  161. if random.random() < CXPB: # cross two individuals with probability CXPB
  162. crossoff1, crossoff2 = self.crossoperate(offspring)
  163. if random.random() < MUTPB: # mutate an individual with probability MUTPB
  164. muteoff1 = self.mutation(crossoff1, self.bound)
  165. muteoff2 = self.mutation(crossoff2, self.bound)
  166. fit_muteoff1 = self.evaluate(muteoff1.data) # Evaluate the individuals
  167. fit_muteoff2 = self.evaluate(muteoff2.data) # Evaluate the individuals
  168. nextoff.append({'Gene': muteoff1, 'fitness': fit_muteoff1})
  169. nextoff.append({'Gene': muteoff2, 'fitness': fit_muteoff2})
  170. else:
  171. fit_crossoff1 = self.evaluate(crossoff1.data) # Evaluate the individuals
  172. fit_crossoff2 = self.evaluate(crossoff2.data)
  173. nextoff.append({'Gene': crossoff1, 'fitness': fit_crossoff1})
  174. nextoff.append({'Gene': crossoff2, 'fitness': fit_crossoff2})
  175. else:
  176. nextoff.extend(offspring)
  177. # The population is entirely replaced by the offspring
  178. self.pop = nextoff
  179. # Gather all the fitnesses in one list and print the stats
  180. fits = [ind['fitness'] for ind in self.pop]
  181. best_ind = self.selectBest(self.pop)
  182. if best_ind['fitness'] > self.bestindividual['fitness']:
  183. self.bestindividual = best_ind
  184. print("Best individual found is {}, {}".format(self.bestindividual['Gene'].data,self.bestindividual['fitness']))
  185. print("Max fitness of current pop: {}".format(max(fits)))
  186. print("------ End of (successful) evolution ------")
  187. from Draw import Draw
  188. if __name__ == "__main__":
  189. CXPB, MUTPB, NGEN, popsize = 0.7, 0.5, 1000, 1000#control parameters
  190. up = [5, 5, 5] # upper range for variables
  191. low = [-2, -2, -2] # lower range for variables
  192. parameter = [CXPB, MUTPB, NGEN, popsize, low, up]
  193. run = GA(parameter)
  194. run.GA_main()
  195. print(run.bestindividual['Gene'].data)
  196. result = []
  197. for i in x:
  198. result.append(myFuc(run.bestindividual['Gene'].data[0], run.bestindividual['Gene'].data[1], run.bestindividual['Gene'].data[2], i))
  199. Draw([y, result], ["real", "predict"], x, "Real and predict", Colors=None)

image.png
image.png

蒙特卡洛算法

还有一种算法就是使用蒙特卡洛进行优化
优化的方法很简单,就是直接随机的生成参数,然后进行尝试,如果优化则保留
简单的基于模拟退火的算法框架实现如下

  1. from __future__ import division
  2. import numpy as np
  3. import matplotlib.pyplot as plt
  4. import math
  5. from Draw import Draw
  6. def realFuction(x):
  7. return -0.02*x**2 +2*x+5*math.sin(x)
  8. def myFuc(a1,a2,a3,x):
  9. return a1*x**2 + a2*x + a3*math.sin(x)
  10. def aimFunction(para,x):
  11. res=0
  12. for i in x:
  13. res += math.sqrt((myFuc(para[0],para[1],para[2],i)-realFuction(i))**2)
  14. return res/len(x)
  15. x = [i for i in range(100)]
  16. y = [0 for i in range(100)]
  17. for i in range(100):
  18. y[i] = realFuction(x[i])
  19. Need=[np.random.uniform(low=-1, high=5) for _ in range(3)]
  20. cur_best=aimFunction(Need,x)
  21. update_time=0
  22. for i in range(100000):
  23. para=[np.random.uniform(low=-1, high=5) for _ in range(3)]
  24. temp_y = aimFunction(para,x)
  25. if temp_y - cur_best < 0:
  26. Need=para
  27. cur_best=temp_y
  28. update_time+=1
  29. print("update!!",update_time,i)
  30. result=[]
  31. print(Need)
  32. for i in x:
  33. result.append(myFuc(Need[0],Need[1],Need[2],i))
  34. Draw([y,result],["real","predict"],x,"Real and predict",Colors=None)

image.png
image.png

备注

绘图函数,基于数学建模选拔赛代码整合封装得到

  1. import numpy as np
  2. import matplotlib.pyplot as plt
  3. from matplotlib.pyplot import MultipleLocator
  4. plt.rcParams['font.sans-serif']=['SimHei'] #用来正常显示中文标签
  5. plt.rcParams['axes.unicode_minus']=False #用来正常显示负号
  6. #families=[ 'fantasy','Tahoma', 'monospace','Times New Roman']
  7. def Draw(Lines,Names,index,Title,Colors=None):
  8. if Colors==None:
  9. Colors=['lightseagreen','orange','red','darkblue','green', ]
  10. plt.rcParams['figure.figsize'] = (12.0, 7.0)
  11. for i in range(len(Lines)):
  12. plt.plot(index,Lines[i],Colors[i],label=Names[i],linewidth=0.8)
  13. Title_font = {'family': 'fantasy','size': 20}
  14. plt.title(Title, Title_font)
  15. font1 = {'family': 'Tahoma','size': 14}
  16. plt.xlabel('X', font1)
  17. plt.ylabel('Y', font1,rotation=0)
  18. #plt.ylabel('Y', font1)
  19. plt.legend()
  20. x_len=len(index)/20
  21. x_major_locator = MultipleLocator(x_len)
  22. ax = plt.gca()
  23. ax.xaxis.set_major_locator(x_major_locator)
  24. plt.show()
  25. #X = np.linspace(-np.pi, np.pi, 256, endpoint=True)
  26. #C,S = np.cos(X), np.sin(X)
  27. #Draw([C,S],['Sin','Cos'],X,"test")