diff --git a/project_1.ipynb b/project_1.ipynb index 20af7bb..a27fcaa 100644 --- a/project_1.ipynb +++ b/project_1.ipynb @@ -20,7 +20,7 @@ }, { "cell_type": "code", - "execution_count": 55, + "execution_count": 112, "id": "9d9c91f7", "metadata": {}, "outputs": [ @@ -28,8 +28,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "Best solution: [0 0 1 1 1 1 0 0 1 0 1 0 1 1 1 1 1 1 1 0 0 1 1 0 0 1 1 0 0 0 1 1 1 1 0 1 1\n", - " 1 0 1 0 1 1 0 1 1 1 1 0 1]\n", + "Best solution: [1 0 1 1 0 1 0 0 1 0 1 1 1 1 0 1 1 1 1 0 1 1 0 1 1 1 1 0 0 1 0 1 1 1 0 1 0\n", + " 1 0 1 0 1 0 1 1 1 0 1 0 1]\n", "Total value: 309.0\n" ] } @@ -130,9 +130,17 @@ "print(\"Total value:\", best_value)\n" ] }, + { + "cell_type": "markdown", + "id": "f3e72e7b", + "metadata": {}, + "source": [ + "#### Knapsack CMA" + ] + }, { "cell_type": "code", - "execution_count": null, + "execution_count": 113, "id": "9be946a4", "metadata": {}, "outputs": [ @@ -140,9 +148,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "Best solution (binary vector): [1 1 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0\n", + "Best solution (binary vector): [0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 1 0 0 0 0 0 0\n", " 0 0 1 1 0 0 1 0 0 0 1 1 0]\n", - "Best total value: 73.0\n" + "Best total value: 70.0\n" ] } ], @@ -301,7 +309,7 @@ }, { "cell_type": "code", - "execution_count": 57, + "execution_count": 114, "id": "8a6129d6", "metadata": {}, "outputs": [], @@ -315,7 +323,7 @@ }, { "cell_type": "code", - "execution_count": 58, + "execution_count": 115, "id": "e75e2f25", "metadata": {}, "outputs": [], @@ -331,7 +339,7 @@ }, { "cell_type": "code", - "execution_count": 59, + "execution_count": 116, "id": "33adaa91", "metadata": {}, "outputs": [], @@ -378,7 +386,7 @@ }, { "cell_type": "code", - "execution_count": 60, + "execution_count": 117, "id": "8b4668c0", "metadata": {}, "outputs": [ @@ -386,7 +394,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "0.0\n" + "6.684735230633176e-14\n" ] } ], @@ -419,7 +427,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 118, "id": "b948ed30", "metadata": {}, "outputs": [], @@ -433,7 +441,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 119, "id": "1aa84235", "metadata": {}, "outputs": [], @@ -451,7 +459,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 120, "id": "b19497d6", "metadata": {}, "outputs": [], @@ -499,7 +507,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 121, "id": "4a91facf", "metadata": {}, "outputs": [ @@ -529,10 +537,195 @@ "print(population[sorted_ind[0]], np.average(fitness(population)))" ] }, + { + "cell_type": "markdown", + "id": "dcc99684", + "metadata": {}, + "source": [ + "#### CMA (for all non-binary problems)" + ] + }, + { + "cell_type": "code", + "execution_count": 125, + "id": "443c16e6", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "class CMAES:\n", + " def __init__(self, n_dim, problem='parabola', \n", + " pop_size=None, mu=None, sigma=None, max_iter=500):\n", + " \"\"\"\n", + " CMA for continuous minimization problems.\n", + " Supports: 'parabola', 'ellipsoid', 'rastrigin'\n", + " \"\"\"\n", + " self.n = int(n_dim)\n", + " self.problem = problem\n", + "\n", + " # CMA-ES parameters\n", + " self.lam = pop_size if pop_size else 4 + int(3 * np.log(self.n + 1))\n", + " self.mu = mu if mu else self.lam // 2\n", + " self.sigma = sigma if sigma else 0.2\n", + " self.max_iter = max_iter\n", + "\n", + " # recombination weights\n", + " w = np.log(self.mu + 0.5) - np.log(np.arange(1, self.mu + 1))\n", + " self.weights = w / np.sum(w)\n", + " self.mueff = np.sum(self.weights)**2 / np.sum(self.weights**2)\n", + "\n", + " # initialization\n", + " self.mean = np.zeros(self.n)\n", + " self.C = np.identity(self.n)\n", + " self.pc = np.zeros(self.n)\n", + " self.ps = np.zeros(self.n)\n", + "\n", + " # constants\n", + " self.cc = (4 + self.mueff/self.n) / (self.n + 4 + 2*self.mueff/self.n)\n", + " self.cs = (self.mueff + 2) / (self.n + self.mueff + 5)\n", + " self.c1 = 2 / ((self.n + 1.3)**2 + self.mueff)\n", + " self.cmu = min(1 - self.c1,\n", + " 2*(self.mueff - 2 + 1/self.mueff) /\n", + " ((self.n + 2)**2 + self.mueff))\n", + " self.damps = 1 + 2*max(0, np.sqrt((self.mueff-1)/(self.n+1)) - 1) + self.cs\n", + "\n", + " # eigen decomposition\n", + " self.B = np.identity(self.n)\n", + " self.D = np.ones(self.n)\n", + " self.inv_sqrt_C = np.identity(self.n)\n", + " self.chiN = np.sqrt(self.n) * (1 - 1/(4*self.n) + 1/(21*self.n**2))\n", + "\n", + " self.history = []\n", + "\n", + " # Objective functions\n", + " def fitness(self, x):\n", + " \"\"\"Return f(x) for the current problem (minimization).\"\"\"\n", + " if self.problem == 'parabola':\n", + " return 10 * np.sum(x**2)\n", + "\n", + " elif self.problem == 'ellipsoid':\n", + " # Rotated hyper-ellipsoid\n", + " x1, x2 = x[0], x[1]\n", + " term1 = (np.sqrt(3)/2*(x1 - 3) + 0.5*(x2 - 5))**2\n", + " term2 = 5*((np.sqrt(3)/2*(x2 - 5) - 0.5*(x1 - 3)))**2\n", + " return term1 + term2\n", + "\n", + " elif self.problem == 'rastrigin':\n", + " d = len(x)\n", + " return 10*d + np.sum((x - 2)**2 - 10*np.cos(2*np.pi*(x - 2)))\n", + "\n", + " else:\n", + " raise ValueError(f\"Unknown problem '{self.problem}'\")\n", + "\n", + " \n", + " # Core CMA logic\n", + " def ask(self):\n", + " \"\"\"Generate new candidate population.\"\"\"\n", + " arz = np.random.randn(self.n, self.lam)\n", + " ary = self.B @ (self.D.reshape(-1, 1) * arz)\n", + " arx = self.mean.reshape(-1, 1) + self.sigma * ary\n", + " return arx.T, arz.T\n", + "\n", + " def run(self):\n", + " best_val = np.inf\n", + " best_x = None\n", + "\n", + " for gen in range(self.max_iter):\n", + " arx, arz = self.ask()\n", + " fits = np.array([self.fitness(x) for x in arx])\n", + " idx = np.argsort(fits)\n", + " arx, arz, fits = arx[idx], arz[idx], fits[idx]\n", + "\n", + " if fits[0] < best_val:\n", + " best_val = fits[0]\n", + " best_x = arx[0]\n", + "\n", + " xold = self.mean\n", + " self.mean = np.dot(self.weights, arx[:self.mu])\n", + " y = np.dot(self.weights, arz[:self.mu])\n", + "\n", + " # evolution paths\n", + " self.ps = (1 - self.cs)*self.ps + np.sqrt(self.cs*(2-self.cs)*self.mueff)*(self.B @ y)\n", + " hsig = int(\n", + " (np.linalg.norm(self.ps)/np.sqrt(1-(1-self.cs)**(2*(gen+1)))/self.chiN)\n", + " < (1.4 + 2/(self.n+1))\n", + " )\n", + " self.pc = (1 - self.cc)*self.pc + hsig*np.sqrt(self.cc*(2-self.cc)*self.mueff)*(self.B @ (self.D*y))\n", + "\n", + " # covariance update\n", + " artmp = (1/self.sigma)*(arx[:self.mu] - xold)\n", + " self.C = (1 - self.c1 - self.cmu)*self.C \\\n", + " + self.c1*(np.outer(self.pc, self.pc) + (1-hsig)*self.cc*(2-self.cc)*self.C) \\\n", + " + self.cmu*np.sum([w*np.outer(ar, ar)\n", + " for w, ar in zip(self.weights, artmp)], axis=0)\n", + "\n", + " # step size\n", + " self.sigma *= np.exp((self.cs/self.damps)*(np.linalg.norm(self.ps)/self.chiN - 1))\n", + "\n", + " # decomposition update\n", + " if gen % (self.n//10 + 1) == 0:\n", + " self.D, self.B = np.linalg.eigh(self.C)\n", + " self.D = np.sqrt(np.maximum(self.D, 1e-20))\n", + " self.inv_sqrt_C = self.B @ np.diag(self.D**-1) @ self.B.T\n", + "\n", + " self.history.append(best_val)\n", + "\n", + " # stop once essentially converged\n", + " if best_val < 1e-40:\n", + " break\n", + "\n", + " return best_x, best_val, self.history\n" + ] + }, + { + "cell_type": "code", + "execution_count": 126, + "id": "1bdb6c52", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Best x: [-1.57456597e-21]\n", + "Best f(x)=10x²: 2.479258000512089e-41\n" + ] + } + ], + "source": [ + "cma_para = CMAES(n_dim=1, problem='parabola', sigma=0.05, max_iter=1000)\n", + "best_x, best_val, hist = cma_para.run()\n", + "print(\"Best x:\", best_x)\n", + "print(\"Best f(x)=10x²:\", best_val)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 127, + "id": "f0d4eba1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Best x: [3. 5.]\n", + "Best f(x): 0.0\n" + ] + } + ], + "source": [ + "cma_ellip = CMAES(n_dim=2, problem='ellipsoid', sigma=0.3, max_iter=600)\n", + "best_x, best_val, hist = cma_ellip.run()\n", + "print(\"Best x:\", best_x)\n", + "print(\"Best f(x):\", best_val)\n" + ] + }, { "cell_type": "code", "execution_count": null, - "id": "e954d523", + "id": "6fa924ed", "metadata": {}, "outputs": [], "source": []