├── requirements.txt ├── LICENSE ├── README.txt ├── least_square_fit.py └── least_square_fit_orthogonal.py /requirements.txt: -------------------------------------------------------------------------------- 1 | autopep8==1.5.7 2 | cycler==0.10.0 3 | kiwisolver==1.3.2 4 | matplotlib==3.4.3 5 | numpy==1.21.2 6 | Pillow==8.4.0 7 | pycodestyle==2.8.0 8 | pyparsing==2.4.7 9 | python-dateutil==2.8.2 10 | six==1.16.0 11 | toml==0.10.2 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | 这本是一次工程数学的作业, least_square_fit_orthogonal.py 那个代码写得自我感觉良好, 遂开源. 2 | 3 | 许可: 使用 [MIT](/LICENSE) 协议 4 | 5 | 下述文本表述了一些编码时的数学逻辑, github 无法正常显示, 遂以纯文本形式保留, 建议贴到 LaTeX 中再查看. 6 | 7 | $$ 8 | 9 | 取拟合函数 S^* = \sum_{i=0}^{n} \phi_i a_i \\ 10 | 取基函数 \phi_i = x^i \\ 11 | 取权重 \omega_i \\ 12 | 取内积计算 (a(x), b(x)) = \sum_{i=0}^{m} a_i(x) b_i(x) \omega_i \\ 13 | 14 | 通过以下等式可以求出拟合函数中的 a_i \\ 15 | 其中 f 为假定的实际函数, 即 f(x_i) = y_i \\ 16 | 17 | \left[ \begin{matrix} 18 | 19 | (\phi_0, \phi_0) & (\phi_0, \phi_1) & \cdots & (\phi_0, \phi_n) \\ 20 | (\phi_1, \phi_0) & (\phi_1, \phi_1) & \cdots & (\phi_1, \phi_n) \\ 21 | \vdots & \vdots & \ddots & \vdots \\ 22 | (\phi_n, \phi_0) & (\phi_n, \phi_1) & \cdots & (\phi_n, \phi_n) \\ 23 | 24 | \end{matrix} \right] 25 | 26 | \left[ \begin{matrix} 27 | 28 | a_0 \\ 29 | a_1 \\ 30 | \vdots \\ 31 | a_n \\ 32 | 33 | \end{matrix} \right] 34 | 35 | = 36 | 37 | \left[ \begin{matrix} 38 | (\phi_0, f) \\ 39 | (\phi_1, f) \\ 40 | \vdots \\ 41 | (\phi_n, f) \\ 42 | \end{matrix} \right] 43 | 44 | $$ 45 | 46 | --- 47 | 48 | $$ 49 | 当所有的基函数正交时, 原矩阵变为对角阵, 拟合函数中的系数 a_i = \frac{(\phi_i, f)}{(\phi_i, \phi_i)} \\ 50 | 51 | 构造正交的基函数的方法如下, P_i 即最高次为 i 的基函数 \\ 52 | 53 | \begin{cases} 54 | P_0(x) = 1 \\ 55 | P_1(x) = x - \alpha_0 \\ 56 | 57 | P_{k+1}(x) = (x - \alpha_k)P_k(x) - \beta_k P_{k-1}(x) \\ 58 | \end{cases} \\ 59 | 60 | 其中 \alpha 和 \beta 的值如下 \\ 61 | 62 | \begin{cases} 63 | 64 | \alpha_k = \frac{(x P_k, P_k)}{(P_k, P_k)} \\ 65 | 66 | \beta_k = \frac{(P_k, P_k)}{(P_{k-1}, P_{k-1})} \\ 67 | 68 | \end{cases} 69 | $$ -------------------------------------------------------------------------------- /least_square_fit.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | 4 | 5 | def get_input_tuple(): 6 | input_x = [0, 0.3, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0] 7 | input_y = [1.20, 1.50, 1.70, 2.00, 2.24, 2.40, 2.75, 3.00] 8 | input_weight = [ 1, 1, 50, 1, 1, 1, 1, 1] 9 | return input_x, input_y, input_weight 10 | 11 | 12 | def calc_inner_product(func1, func2, input_x, input_weight): 13 | assert len(input_x) == len(input_weight) 14 | 15 | res = 0 16 | for x, w in zip(input_x, input_weight): 17 | res += func1(x) * func2(x) * w 18 | 19 | return res 20 | 21 | 22 | def assumed_actual_func(input_x, input_y): 23 | 24 | def func(x): 25 | for i in range(len(input_x)): 26 | if x == input_x[i]: 27 | return input_y[i] 28 | raise Exception('x not exist in input_x') 29 | 30 | return func 31 | 32 | 33 | def get_base_func(sub_num): 34 | if sub_num == 0: 35 | return lambda x : 1 36 | if sub_num == 1: 37 | return lambda x : x 38 | if sub_num == 2: 39 | return lambda x : pow(x, 2) 40 | 41 | raise Exception('undefined base func for sub_num: %d' % sub_num) 42 | 43 | 44 | def solve(): 45 | input_x, input_y, input_weight = get_input_tuple() 46 | 47 | assert len(input_x) == len(input_y) and len(input_y) == len(input_weight) 48 | 49 | assumed_func = assumed_actual_func(input_x, input_y) 50 | 51 | base_funcs = [get_base_func(i) for i in range(2 + 1)] 52 | 53 | # Ax = y, A_{n*n}, x_{n*1}, y_{n*1} 54 | A = np.zeros((2+1, 2+1)) 55 | for i in range(2 + 1): 56 | for j in range(2 + 1): 57 | A[i, j] = calc_inner_product(base_funcs[i], base_funcs[j], input_x, input_weight) 58 | 59 | y = np.zeros((2+1, 1)) 60 | for i in range(2+1): 61 | y[i][0] = calc_inner_product(base_funcs[i], assumed_func, input_x, input_weight) 62 | 63 | x = np.linalg.solve(A, y) # a_0, a_1, a_2 64 | 65 | def fit_func(param): 66 | res = 0 67 | for i in range(2+1): 68 | res += x[i] * base_funcs[i](param) 69 | return res 70 | 71 | draw(input_x, input_y, fit_func, './out.png') 72 | 73 | print(x) 74 | print('function:', '%.3fx^2 + %.3fx + %.3f' % (x[2], x[1], x[0])) 75 | 76 | def draw(input_x, input_y, func, out_path): 77 | fig, ax = plt.subplots() 78 | ax.scatter(input_x, input_y) 79 | 80 | fit_x = np.linspace(0, 1, 1000) 81 | fit_y = np.array([func(x) for x in fit_x]) 82 | ax.plot(fit_x, fit_y) 83 | plt.savefig(out_path) 84 | 85 | 86 | def main(): 87 | solve() 88 | 89 | 90 | if __name__ == '__main__': 91 | main() 92 | -------------------------------------------------------------------------------- /least_square_fit_orthogonal.py: -------------------------------------------------------------------------------- 1 | from least_square_fit import calc_inner_product 2 | from least_square_fit import get_input_tuple 3 | from least_square_fit import assumed_actual_func 4 | from least_square_fit import draw 5 | 6 | def another_sample_tuple(): 7 | input_x = [0, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0] 8 | input_y = [1.00, 1.75, 1.96, 2.19, 2.44, 2.71, 3.00] 9 | input_weight = [1, 1, 1, 1, 1, 1, 1] 10 | return input_x, input_y, input_weight 11 | 12 | 13 | class orthogonal_func: 14 | def __init__(self, coefficient=[]): 15 | # sample: 3*x^2 + 4*x + 2, self.coefficient=[2, 4, 3] 16 | self.coefficient = coefficient 17 | 18 | self._remove_useless_zeros() 19 | 20 | def _remove_useless_zeros(self): 21 | i = len(self.coefficient) - 1 22 | while i > 0: 23 | if self.coefficient[i] != 0: 24 | break 25 | i -= 1 26 | self.coefficient = self.coefficient[0:i+1] 27 | 28 | def multi_k(self, k): 29 | return orthogonal_func([k * x for x in self.coefficient]) 30 | 31 | def multi_x(self): 32 | ''' 33 | move all coefficient to right and insert 0 at front. 34 | this is multiple by x. 35 | ''' 36 | coefficient = self.coefficient.copy() 37 | coefficient = [0] + coefficient 38 | return orthogonal_func(coefficient) 39 | 40 | def negate(self): 41 | return orthogonal_func([-x for x in self.coefficient]) 42 | 43 | def add(self, other): 44 | max_len = max(len(self.coefficient), len(other.coefficient)) 45 | coefficient = [0] * max_len 46 | 47 | for i in range(len(self.coefficient)): 48 | coefficient[i] = self.coefficient[i] 49 | 50 | for i in range(len(other.coefficient)): 51 | coefficient[i] += other.coefficient[i] 52 | 53 | return orthogonal_func(coefficient) 54 | 55 | def minus(self, other): 56 | return self.add(other.negate()) 57 | 58 | def __call__(self, x): 59 | res = 0 60 | tmp_x = 1 61 | for i in range(len(self.coefficient)): 62 | res += tmp_x * self.coefficient[i] 63 | tmp_x *= x 64 | return res 65 | 66 | def __repr__(self): 67 | return self.__str__() 68 | 69 | def __str__(self): 70 | s = '' 71 | for i in range(len(self.coefficient)-1, -1, -1): 72 | if i == 0: 73 | tmp = '%.3f' % self.coefficient[i] 74 | elif i == 1: 75 | tmp = '%.3fx' % self.coefficient[i] 76 | else: 77 | tmp = '%.3fx^%d' % (self.coefficient[i], i) 78 | 79 | s += ' + %s' % tmp 80 | return s.strip(' +') 81 | 82 | 83 | class orthogonal_generator: 84 | r''' 85 | $$ 86 | \begin{cases} 87 | P_0(x) = 1 \\ 88 | P_1(x) = x - \alpha_0 \\ 89 | 90 | P_{k+1}(x) = (x - \alpha_k)P_k(x) - \beta_k P_{k-1}(x) \\ 91 | \end{cases} \\ 92 | 93 | \begin{cases} 94 | 95 | \alpha_k = \frac{(x P_k, P_k)}{(P_k, P_k)} \\ 96 | 97 | \beta_k = \frac{(P_k, P_k)}{(P_{k-1}, P_{k-1})} \\ 98 | 99 | \end{cases} 100 | $$ 101 | ''' 102 | 103 | def __init__(self, input_x, input_weight): 104 | self.P = {} 105 | self.alpha = {} 106 | self.beta = {} 107 | 108 | self.input_x = input_x 109 | self.input_weight = input_weight 110 | 111 | self.cached = -1 112 | self._pre_generate() 113 | 114 | def _pre_generate(self): 115 | self.P[0] = orthogonal_func([1]) 116 | self.alpha[0] = self._calc_alpha(self.P[0]) 117 | self.P[1] = orthogonal_func([-self.alpha[0], 1]) 118 | self.alpha[1] = self._calc_alpha(self.P[1]) 119 | 120 | self.beta[0] = 0 # use beta_0 = 0 for comfortable 121 | self.beta[1] = self._calc_beta(self.P[0], self.P[1]) 122 | 123 | self.cached = 1 124 | 125 | def _calc_alpha(self, P_k): 126 | ''' 127 | need P_k, return alpha_k 128 | ''' 129 | x_P_k = P_k.multi_x() 130 | 131 | tmp1 = calc_inner_product(x_P_k, P_k, self.input_x, self.input_weight) 132 | tmp2 = calc_inner_product(P_k, P_k, self.input_x, self.input_weight) 133 | 134 | return tmp1 / tmp2 135 | 136 | def _calc_beta(self, P_k_minus_1, P_k): 137 | ''' 138 | need P_{k-1} and P_k, return beta_k 139 | ''' 140 | tmp1 = calc_inner_product(P_k, P_k, self.input_x, self.input_weight) 141 | tmp2 = calc_inner_product(P_k_minus_1, P_k_minus_1, self.input_x, self.input_weight) 142 | 143 | return tmp1 / tmp2 144 | 145 | def generate(self, k): 146 | if k <= self.cached: 147 | return self.P[k] 148 | 149 | for i in range(self.cached, k): 150 | r''' 151 | P_{k+1}(x) = (x - \alpha_k)P_k(x) - \beta_k P_{k-1}(x) \\ 152 | = x P_k(x) - \alpha_k P_k(x) - \beta_k P_{k-1}(x) 153 | tmp1 tmp2 tmp3 154 | ''' 155 | 156 | tmp1 = self.P[i].multi_x() 157 | tmp2 = self.P[i].multi_k(self.alpha[i]) 158 | tmp3 = self.P[i-1].multi_k(self.beta[i]) 159 | 160 | self.P[i+1] = tmp1.minus(tmp2).minus(tmp3) 161 | self.alpha[i+1] = self._calc_alpha(self.P[i+1]) 162 | self.beta[i+1] = self._calc_beta(self.P[i], self.P[i+1]) 163 | 164 | self.cached = k 165 | return self.P[k] 166 | 167 | 168 | def test1(): 169 | f1 = orthogonal_func([1, 2, 3]) # 1 + 2*x + 3x^2 170 | f2 = f1.multi_k(2) # 2 + 4*x + 6*x^2 171 | f3 = f1.multi_x() # 0 + x + 2*x^2 + 3*x^3 172 | f4 = f2.add(f3) 173 | f5 = f4.minus(f3) 174 | f6 = orthogonal_func([1, 0, 0]) 175 | f7 = orthogonal_func([0, 0, 0]) 176 | f8 = f7.add(f5) 177 | 178 | print(f1(1)) 179 | print(f2(1)) 180 | print(f3(1)) 181 | print(f4(1)) 182 | print(f5(1)) 183 | 184 | print(f1(2)) 185 | print(f2(2)) 186 | print(f3(2)) 187 | print(f4(2)) 188 | print(f5(2)) 189 | 190 | print(f1) 191 | print(f2) 192 | print(f3) 193 | print(f4) 194 | print(f5) 195 | print(f6) 196 | print(f7) 197 | print(f8) 198 | 199 | def test2(): 200 | input_x, input_y, input_weight = get_input_tuple() 201 | g = orthogonal_generator(input_x, input_weight) 202 | 203 | print(g.generate(0)) 204 | print(g.generate(1)) 205 | print(g.generate(2)) 206 | print(g.generate(3)) 207 | print(g.generate(4)) 208 | print(g.generate(5)) 209 | 210 | def main(): 211 | K = 2 212 | 213 | P = [] 214 | a = [] 215 | 216 | input_x, input_y, input_weight = get_input_tuple() 217 | og = orthogonal_generator(input_x, input_weight) 218 | 219 | for i in range(K+1): 220 | P.append(og.generate(i)) 221 | 222 | actual_func = assumed_actual_func(input_x, input_y) 223 | for i in range(K+1): 224 | tmp1 = calc_inner_product(P[i], actual_func, input_x, input_weight) 225 | tmp2 = calc_inner_product(P[i], P[i], input_x, input_weight) 226 | a.append(tmp1 / tmp2) 227 | 228 | fit_func = orthogonal_func([0]) 229 | for i in range(K+1): 230 | fit_func = fit_func.add(P[i].multi_k(a[i])) 231 | 232 | print('P:', P) 233 | print('a:', a) 234 | print('alpha:', og.alpha) 235 | print('beta:', og.beta) 236 | print('function:', fit_func) 237 | 238 | draw(input_x, input_y, fit_func, './out2.png') 239 | 240 | 241 | if __name__ == '__main__': 242 | main() 243 | --------------------------------------------------------------------------------