├── .gitignore ├── blog_data ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png ├── exp_1.PNG ├── exp_2.PNG ├── young.png ├── contract.PNG ├── hyperbox.png ├── classifier.PNG ├── expansion.PNG ├── membership.PNG ├── architecture.PNG ├── overlap test.PNG ├── fuzzy_animation.mp4 ├── fuzzy_animation_iris.mp4 └── fuzzy_animation_circle.mp4 ├── README.md ├── iris.data └── fuzzy.py /.gitignore: -------------------------------------------------------------------------------- 1 | /__pycache__ 2 | /.ipynb_checkpoints 3 | *.pyc 4 | .gitignore -------------------------------------------------------------------------------- /blog_data/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cartmanishere/fuzzy-min-max-classifier/HEAD/blog_data/1.png -------------------------------------------------------------------------------- /blog_data/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cartmanishere/fuzzy-min-max-classifier/HEAD/blog_data/2.png -------------------------------------------------------------------------------- /blog_data/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cartmanishere/fuzzy-min-max-classifier/HEAD/blog_data/3.png -------------------------------------------------------------------------------- /blog_data/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cartmanishere/fuzzy-min-max-classifier/HEAD/blog_data/4.png -------------------------------------------------------------------------------- /blog_data/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cartmanishere/fuzzy-min-max-classifier/HEAD/blog_data/5.png -------------------------------------------------------------------------------- /blog_data/exp_1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cartmanishere/fuzzy-min-max-classifier/HEAD/blog_data/exp_1.PNG -------------------------------------------------------------------------------- /blog_data/exp_2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cartmanishere/fuzzy-min-max-classifier/HEAD/blog_data/exp_2.PNG -------------------------------------------------------------------------------- /blog_data/young.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cartmanishere/fuzzy-min-max-classifier/HEAD/blog_data/young.png -------------------------------------------------------------------------------- /blog_data/contract.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cartmanishere/fuzzy-min-max-classifier/HEAD/blog_data/contract.PNG -------------------------------------------------------------------------------- /blog_data/hyperbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cartmanishere/fuzzy-min-max-classifier/HEAD/blog_data/hyperbox.png -------------------------------------------------------------------------------- /blog_data/classifier.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cartmanishere/fuzzy-min-max-classifier/HEAD/blog_data/classifier.PNG -------------------------------------------------------------------------------- /blog_data/expansion.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cartmanishere/fuzzy-min-max-classifier/HEAD/blog_data/expansion.PNG -------------------------------------------------------------------------------- /blog_data/membership.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cartmanishere/fuzzy-min-max-classifier/HEAD/blog_data/membership.PNG -------------------------------------------------------------------------------- /blog_data/architecture.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cartmanishere/fuzzy-min-max-classifier/HEAD/blog_data/architecture.PNG -------------------------------------------------------------------------------- /blog_data/overlap test.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cartmanishere/fuzzy-min-max-classifier/HEAD/blog_data/overlap test.PNG -------------------------------------------------------------------------------- /blog_data/fuzzy_animation.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cartmanishere/fuzzy-min-max-classifier/HEAD/blog_data/fuzzy_animation.mp4 -------------------------------------------------------------------------------- /blog_data/fuzzy_animation_iris.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cartmanishere/fuzzy-min-max-classifier/HEAD/blog_data/fuzzy_animation_iris.mp4 -------------------------------------------------------------------------------- /blog_data/fuzzy_animation_circle.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cartmanishere/fuzzy-min-max-classifier/HEAD/blog_data/fuzzy_animation_circle.mp4 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Fuzzy Min-Max Classifier: 2 | 3 | An implmenetation of Fuzzy min-max classifer first introduced by Patrick Simpson in [this paper](http://ieeexplore.ieee.org/stamp/redirect.jsp?arnumber=/72/4097/00159066.pdf) in 1992. 4 | 5 | This repository contains the code for my blog post explaning the working and learning algorithm for this classifier. 6 | 7 | ### [Blog link](https://medium.com/@apbetahouse45/understanding-fuzzy-neural-network-with-code-and-graphs-263d1091d773) 8 | 9 | You'll find example usage in the included jupyter notebook. 10 | 11 | ### License : 12 | 13 | The code in this repository is licensed under the terms of the MIT license. 14 | 15 | *Boil it, smash it, stick it in a stew* -------------------------------------------------------------------------------- /iris.data: -------------------------------------------------------------------------------- 1 | 5.1,3.5,1.4,0.2,Iris-setosa 2 | 4.9,3.0,1.4,0.2,Iris-setosa 3 | 4.7,3.2,1.3,0.2,Iris-setosa 4 | 4.6,3.1,1.5,0.2,Iris-setosa 5 | 5.0,3.6,1.4,0.2,Iris-setosa 6 | 5.4,3.9,1.7,0.4,Iris-setosa 7 | 4.6,3.4,1.4,0.3,Iris-setosa 8 | 5.0,3.4,1.5,0.2,Iris-setosa 9 | 4.4,2.9,1.4,0.2,Iris-setosa 10 | 4.9,3.1,1.5,0.1,Iris-setosa 11 | 5.4,3.7,1.5,0.2,Iris-setosa 12 | 4.8,3.4,1.6,0.2,Iris-setosa 13 | 4.8,3.0,1.4,0.1,Iris-setosa 14 | 4.3,3.0,1.1,0.1,Iris-setosa 15 | 5.8,4.0,1.2,0.2,Iris-setosa 16 | 5.7,4.4,1.5,0.4,Iris-setosa 17 | 5.4,3.9,1.3,0.4,Iris-setosa 18 | 5.1,3.5,1.4,0.3,Iris-setosa 19 | 5.7,3.8,1.7,0.3,Iris-setosa 20 | 5.1,3.8,1.5,0.3,Iris-setosa 21 | 5.4,3.4,1.7,0.2,Iris-setosa 22 | 5.1,3.7,1.5,0.4,Iris-setosa 23 | 4.6,3.6,1.0,0.2,Iris-setosa 24 | 5.1,3.3,1.7,0.5,Iris-setosa 25 | 4.8,3.4,1.9,0.2,Iris-setosa 26 | 5.0,3.0,1.6,0.2,Iris-setosa 27 | 5.0,3.4,1.6,0.4,Iris-setosa 28 | 5.2,3.5,1.5,0.2,Iris-setosa 29 | 5.2,3.4,1.4,0.2,Iris-setosa 30 | 4.7,3.2,1.6,0.2,Iris-setosa 31 | 4.8,3.1,1.6,0.2,Iris-setosa 32 | 5.4,3.4,1.5,0.4,Iris-setosa 33 | 5.2,4.1,1.5,0.1,Iris-setosa 34 | 5.5,4.2,1.4,0.2,Iris-setosa 35 | 4.9,3.1,1.5,0.1,Iris-setosa 36 | 5.0,3.2,1.2,0.2,Iris-setosa 37 | 5.5,3.5,1.3,0.2,Iris-setosa 38 | 4.9,3.1,1.5,0.1,Iris-setosa 39 | 4.4,3.0,1.3,0.2,Iris-setosa 40 | 5.1,3.4,1.5,0.2,Iris-setosa 41 | 5.0,3.5,1.3,0.3,Iris-setosa 42 | 4.5,2.3,1.3,0.3,Iris-setosa 43 | 4.4,3.2,1.3,0.2,Iris-setosa 44 | 5.0,3.5,1.6,0.6,Iris-setosa 45 | 5.1,3.8,1.9,0.4,Iris-setosa 46 | 4.8,3.0,1.4,0.3,Iris-setosa 47 | 5.1,3.8,1.6,0.2,Iris-setosa 48 | 4.6,3.2,1.4,0.2,Iris-setosa 49 | 5.3,3.7,1.5,0.2,Iris-setosa 50 | 5.0,3.3,1.4,0.2,Iris-setosa 51 | 7.0,3.2,4.7,1.4,Iris-versicolor 52 | 6.4,3.2,4.5,1.5,Iris-versicolor 53 | 6.9,3.1,4.9,1.5,Iris-versicolor 54 | 5.5,2.3,4.0,1.3,Iris-versicolor 55 | 6.5,2.8,4.6,1.5,Iris-versicolor 56 | 5.7,2.8,4.5,1.3,Iris-versicolor 57 | 6.3,3.3,4.7,1.6,Iris-versicolor 58 | 4.9,2.4,3.3,1.0,Iris-versicolor 59 | 6.6,2.9,4.6,1.3,Iris-versicolor 60 | 5.2,2.7,3.9,1.4,Iris-versicolor 61 | 5.0,2.0,3.5,1.0,Iris-versicolor 62 | 5.9,3.0,4.2,1.5,Iris-versicolor 63 | 6.0,2.2,4.0,1.0,Iris-versicolor 64 | 6.1,2.9,4.7,1.4,Iris-versicolor 65 | 5.6,2.9,3.6,1.3,Iris-versicolor 66 | 6.7,3.1,4.4,1.4,Iris-versicolor 67 | 5.6,3.0,4.5,1.5,Iris-versicolor 68 | 5.8,2.7,4.1,1.0,Iris-versicolor 69 | 6.2,2.2,4.5,1.5,Iris-versicolor 70 | 5.6,2.5,3.9,1.1,Iris-versicolor 71 | 5.9,3.2,4.8,1.8,Iris-versicolor 72 | 6.1,2.8,4.0,1.3,Iris-versicolor 73 | 6.3,2.5,4.9,1.5,Iris-versicolor 74 | 6.1,2.8,4.7,1.2,Iris-versicolor 75 | 6.4,2.9,4.3,1.3,Iris-versicolor 76 | 6.6,3.0,4.4,1.4,Iris-versicolor 77 | 6.8,2.8,4.8,1.4,Iris-versicolor 78 | 6.7,3.0,5.0,1.7,Iris-versicolor 79 | 6.0,2.9,4.5,1.5,Iris-versicolor 80 | 5.7,2.6,3.5,1.0,Iris-versicolor 81 | 5.5,2.4,3.8,1.1,Iris-versicolor 82 | 5.5,2.4,3.7,1.0,Iris-versicolor 83 | 5.8,2.7,3.9,1.2,Iris-versicolor 84 | 6.0,2.7,5.1,1.6,Iris-versicolor 85 | 5.4,3.0,4.5,1.5,Iris-versicolor 86 | 6.0,3.4,4.5,1.6,Iris-versicolor 87 | 6.7,3.1,4.7,1.5,Iris-versicolor 88 | 6.3,2.3,4.4,1.3,Iris-versicolor 89 | 5.6,3.0,4.1,1.3,Iris-versicolor 90 | 5.5,2.5,4.0,1.3,Iris-versicolor 91 | 5.5,2.6,4.4,1.2,Iris-versicolor 92 | 6.1,3.0,4.6,1.4,Iris-versicolor 93 | 5.8,2.6,4.0,1.2,Iris-versicolor 94 | 5.0,2.3,3.3,1.0,Iris-versicolor 95 | 5.6,2.7,4.2,1.3,Iris-versicolor 96 | 5.7,3.0,4.2,1.2,Iris-versicolor 97 | 5.7,2.9,4.2,1.3,Iris-versicolor 98 | 6.2,2.9,4.3,1.3,Iris-versicolor 99 | 5.1,2.5,3.0,1.1,Iris-versicolor 100 | 5.7,2.8,4.1,1.3,Iris-versicolor 101 | 6.3,3.3,6.0,2.5,Iris-virginica 102 | 5.8,2.7,5.1,1.9,Iris-virginica 103 | 7.1,3.0,5.9,2.1,Iris-virginica 104 | 6.3,2.9,5.6,1.8,Iris-virginica 105 | 6.5,3.0,5.8,2.2,Iris-virginica 106 | 7.6,3.0,6.6,2.1,Iris-virginica 107 | 4.9,2.5,4.5,1.7,Iris-virginica 108 | 7.3,2.9,6.3,1.8,Iris-virginica 109 | 6.7,2.5,5.8,1.8,Iris-virginica 110 | 7.2,3.6,6.1,2.5,Iris-virginica 111 | 6.5,3.2,5.1,2.0,Iris-virginica 112 | 6.4,2.7,5.3,1.9,Iris-virginica 113 | 6.8,3.0,5.5,2.1,Iris-virginica 114 | 5.7,2.5,5.0,2.0,Iris-virginica 115 | 5.8,2.8,5.1,2.4,Iris-virginica 116 | 6.4,3.2,5.3,2.3,Iris-virginica 117 | 6.5,3.0,5.5,1.8,Iris-virginica 118 | 7.7,3.8,6.7,2.2,Iris-virginica 119 | 7.7,2.6,6.9,2.3,Iris-virginica 120 | 6.0,2.2,5.0,1.5,Iris-virginica 121 | 6.9,3.2,5.7,2.3,Iris-virginica 122 | 5.6,2.8,4.9,2.0,Iris-virginica 123 | 7.7,2.8,6.7,2.0,Iris-virginica 124 | 6.3,2.7,4.9,1.8,Iris-virginica 125 | 6.7,3.3,5.7,2.1,Iris-virginica 126 | 7.2,3.2,6.0,1.8,Iris-virginica 127 | 6.2,2.8,4.8,1.8,Iris-virginica 128 | 6.1,3.0,4.9,1.8,Iris-virginica 129 | 6.4,2.8,5.6,2.1,Iris-virginica 130 | 7.2,3.0,5.8,1.6,Iris-virginica 131 | 7.4,2.8,6.1,1.9,Iris-virginica 132 | 7.9,3.8,6.4,2.0,Iris-virginica 133 | 6.4,2.8,5.6,2.2,Iris-virginica 134 | 6.3,2.8,5.1,1.5,Iris-virginica 135 | 6.1,2.6,5.6,1.4,Iris-virginica 136 | 7.7,3.0,6.1,2.3,Iris-virginica 137 | 6.3,3.4,5.6,2.4,Iris-virginica 138 | 6.4,3.1,5.5,1.8,Iris-virginica 139 | 6.0,3.0,4.8,1.8,Iris-virginica 140 | 6.9,3.1,5.4,2.1,Iris-virginica 141 | 6.7,3.1,5.6,2.4,Iris-virginica 142 | 6.9,3.1,5.1,2.3,Iris-virginica 143 | 5.8,2.7,5.1,1.9,Iris-virginica 144 | 6.8,3.2,5.9,2.3,Iris-virginica 145 | 6.7,3.3,5.7,2.5,Iris-virginica 146 | 6.7,3.0,5.2,2.3,Iris-virginica 147 | 6.3,2.5,5.0,1.9,Iris-virginica 148 | 6.5,3.0,5.2,2.0,Iris-virginica 149 | 6.2,3.4,5.4,2.3,Iris-virginica 150 | 5.9,3.0,5.1,1.8,Iris-virginica 151 | 152 | -------------------------------------------------------------------------------- /fuzzy.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | from matplotlib import pyplot as plt 4 | from matplotlib import animation 5 | import os 6 | 7 | class Animator: 8 | ''' 9 | An animator class only for animating 2D hyperboxes 10 | ''' 11 | 12 | def __init__(self, box_history, train_patterns, classes, frame_rate, exp_bound, sensitivity, 13 | filename='fuzzy_animation', verbose=True): 14 | # TODO: Customizable parameters 15 | assert len(box_history) == len(train_patterns), '{} (box-history) != {} (train_patterns)'.format(len(box_history), len(train_patterns)) 16 | assert len(train_patterns[0][0]) == 2, 'Only 2D points are allowed.' 17 | 18 | self.fig = plt.figure() 19 | self.fig.set_dpi(100) 20 | self.fig.set_size_inches(7, 6.5) 21 | self.fig.suptitle('Fuzzy min-max classifier') 22 | if filename == '': 23 | filename = 'fuzzy_animation' 24 | self.filename = filename + '.mp4' 25 | self.box_history = box_history 26 | self.train_patterns = train_patterns 27 | self.classes = classes 28 | self.verbose = verbose 29 | 30 | self.frames = np.ravel(np.array([[i]*frame_rate for i in range(len(box_history))])) 31 | self.total = len(box_history) 32 | 33 | self.ax = plt.axes(xlim=(0, 1), ylim=(0, 1)) 34 | self.ax.set_title('θ = {} and γ = {}'.format(exp_bound, sensitivity)) 35 | self.rectangles = [] 36 | self.scatters = [] 37 | self.colormap = [np.array([255, 0, 0]), np.array([0, 0, 255])] + [self.__get_random_color() for i in range(len(np.unique(classes)) - 2)] 38 | 39 | for i in range((len(train_patterns))): 40 | x, y = train_patterns[i] 41 | y = int(y) 42 | if y == 0: 43 | self.scatters.append(plt.scatter(-1, -1, c=tuple(self.colormap[y] / 255))) 44 | else: 45 | self.scatters.append(plt.scatter(-1, -1, c=tuple(self.colormap[y] / 255))) 46 | 47 | for _class in classes: 48 | if _class == 0: 49 | self.rectangles.append(plt.Rectangle((0, 0), 0, 0, fill=False, color='r')) 50 | else: 51 | self.rectangles.append(plt.Rectangle((0, 0), 0, 0, fill=False, color='b')) 52 | 53 | if self.verbose: 54 | print('{:<20}: {:<10}'.format('Total Boxes', len(self.rectangles))) 55 | print('{:<20}: {:<10}'.format('Points to plot', len(self.scatters))) 56 | 57 | 58 | def __get_random_color(self): 59 | r = lambda: random.randint(0,255) 60 | return np.array([r(), r(), r()]) 61 | 62 | 63 | def box_to_rect(self, box): 64 | vj, wj = box 65 | height = wj[1] - vj[1] 66 | width = wj[0] - vj[0] 67 | return tuple(vj), width, height 68 | 69 | 70 | def init(self): 71 | for i in self.rectangles: 72 | self.ax.add_patch(i) 73 | 74 | return tuple(self.rectangles) + tuple(self.scatters) 75 | 76 | 77 | def _animate(self, i): 78 | hyperboxes = self.box_history[i] 79 | # Plot training point 80 | x, y = self.train_patterns[i] 81 | self.scatters[i].set_offsets(tuple(x)) 82 | for box in range(len(hyperboxes)): 83 | base, width, height = self.box_to_rect(hyperboxes[box]) 84 | self.rectangles[box].set_xy(base) 85 | if width == 0: 86 | width = 0.02 87 | if height == 0: 88 | height = 0.02 89 | 90 | self.rectangles[box].set_width(width) 91 | self.rectangles[box].set_height(height) 92 | 93 | if self.verbose: 94 | print('{:<20}: {}/{}'.format('Animating frame', i+1, self.total), end='\r') 95 | 96 | return tuple(self.rectangles) + tuple(self.scatters) 97 | 98 | 99 | def animate(self): 100 | ''' 101 | Main function to start animation 102 | ''' 103 | 104 | anim = animation.FuncAnimation(self.fig, self._animate, 105 | init_func = self.init, 106 | frames = self.frames, 107 | interval = 20, 108 | blit = True) 109 | 110 | anim.save(self.filename, fps=30, 111 | extra_args=['-vcodec', 'h264', 112 | '-pix_fmt', 'yuv420p']) 113 | 114 | if self.verbose: 115 | print('Animation complete! Video saved at {}'.format(os.path.join(os.getcwd(), self.filename))) 116 | 117 | 118 | class FuzzyMMC: 119 | 120 | def __init__(self, sensitivity=1, exp_bound=1, animate=False): 121 | ''' 122 | Constructor for FuzzyMMC class 123 | ''' 124 | self.sensitivity = sensitivity 125 | self.hyperboxes = None 126 | self.isanimate = animate 127 | self.classes = np.array([]) 128 | self.exp_bound = exp_bound 129 | 130 | if self.animate: 131 | self.box_history = [] 132 | self.train_patterns = [] 133 | 134 | 135 | def membership(self, pattern): 136 | ''' 137 | Calculates membership values a pattern 138 | 139 | Returns an ndarray of membership values of all hyperboxes 140 | ''' 141 | min_pts = self.hyperboxes[:, 0, :] 142 | max_pts = self.hyperboxes[:, 1, :] 143 | 144 | a = np.maximum(0, (1 - np.maximum(0, (self.sensitivity * np.minimum(1, pattern - max_pts))))) 145 | b = np.maximum(0, (1 - np.maximum(0, (self.sensitivity * np.minimum(1, min_pts - pattern))))) 146 | 147 | return np.sum(a + b, axis=1) / (2 * len(pattern)) 148 | 149 | 150 | def overlap_contract(self, index): 151 | ''' 152 | Check if any classwise dissimilar hyperboxes overlap 153 | ''' 154 | contracted = False 155 | for test_box in range(len(self.hyperboxes)): 156 | 157 | if self.classes[test_box] == self.classes[index]: 158 | # Ignore same class hyperbox overlap 159 | continue 160 | 161 | expanded_box = self.hyperboxes[index] 162 | box = self.hyperboxes[test_box] 163 | 164 | ## TODO: Refactor for vectorization 165 | vj, wj = expanded_box 166 | vk, wk = box 167 | 168 | delta_new = delta_old = 1 169 | min_overlap_index = -1 170 | for i in range(len(vj)): 171 | if vj[i] < vk[i] < wj[i] < wk[i]: 172 | delta_new = min(delta_old, wj[i] - vk[i]) 173 | 174 | elif vk[i] < vj[i] < wk[i] < wj[i]: 175 | delta_new = min(delta_old, wk[i] - vj[i]) 176 | 177 | elif vj[i] < vk[i] < wk[i] < wj[i]: 178 | delta_new = min(delta_old, min(wj[i] - vk[i], wk[i] - vj[i])) 179 | 180 | elif vk[i] < vj[i] < wj[i] < wk[i]: 181 | delta_new = min(delta_old, min(wj[i] - vk[i], wk[i] - vj[i])) 182 | 183 | if delta_old - delta_new > 0: 184 | min_overlap_index = i 185 | delta_old = delta_new 186 | 187 | if min_overlap_index >= 0: 188 | i = min_overlap_index 189 | # We need to contract the expanded box 190 | if vj[i] < vk[i] < wj[i] < wk[i]: 191 | vk[i] = wj[i] = (vk[i] + wj[i])/2 192 | 193 | elif vk[i] < vj[i] < wk[i] < wj[i]: 194 | vj[i] = wk[i] = (vj[i] + wk[i])/2 195 | 196 | elif vj[i] < vk[i] < wk[i] < wj[i]: 197 | if (wj[i] - vk[i]) > (wk[i] - vj[i]): 198 | vj[i] = wk[i] 199 | 200 | else: 201 | wj[i] = vk[i] 202 | 203 | elif vk[i] < vj[i] < wj[i] < wk[i]: 204 | if (wk[i] - vj[i]) > (wj[i] - vk[i]): 205 | vk[i] = wj[i] 206 | 207 | else: 208 | wk[i] = vj[i] 209 | 210 | self.hyperboxes[test_box] = np.array([vk, wk]) 211 | self.hyperboxes[index] = np.array([vj, wj]) 212 | contracted = True 213 | 214 | return contracted 215 | 216 | 217 | 218 | def train_pattern(self, X, Y): 219 | ''' 220 | Main function that trains a fuzzy min max classifier 221 | Note: 222 | Y is a one-hot encoded target variable 223 | ''' 224 | target = Y 225 | 226 | if target not in self.classes: 227 | 228 | # Create a new hyberbox 229 | if self.hyperboxes is not None: 230 | self.hyperboxes = np.vstack((self.hyperboxes, np.array([[X, X]]))) 231 | self.classes = np.hstack((self.classes, np.array([target]))) 232 | 233 | else: 234 | self.hyperboxes = np.array([[X, X]]) 235 | self.classes = np.array([target]) 236 | 237 | if self.isanimate: 238 | self.box_history.append(np.copy(self.hyperboxes)) 239 | self.train_patterns.append((X, Y)) 240 | else: 241 | 242 | memberships = self.membership(X) 243 | memberships[np.where(self.classes != target)] = 0 244 | memberships = sorted(list(enumerate(memberships)), key=lambda x: x[1], reverse=True) 245 | 246 | # Expand the most suitable hyperbox 247 | count = 0 248 | while True: 249 | index = memberships[count][0] 250 | min_new = np.minimum(self.hyperboxes[index, 0, :], X) 251 | max_new = np.maximum(self.hyperboxes[index, 1, :], X) 252 | 253 | if self.exp_bound * len(np.unique(self.classes)) >= np.sum(max_new - min_new): 254 | self.hyperboxes[index, 0] = min_new 255 | self.hyperboxes[index, 1] = max_new 256 | break 257 | else: 258 | count += 1 259 | 260 | if count == len(memberships): 261 | self.hyperboxes = np.vstack((self.hyperboxes, np.array([[X, X]]))) 262 | self.classes = np.hstack((self.classes, np.array([target]))) 263 | index = len(self.hyperboxes) - 1 264 | break 265 | 266 | # Overlap test 267 | if self.isanimate: 268 | self.box_history.append(np.copy(self.hyperboxes)) 269 | self.train_patterns.append((X, Y)) 270 | 271 | contracted = self.overlap_contract(index) 272 | 273 | if self.isanimate and contracted: 274 | self.box_history.append(np.copy(self.hyperboxes)) 275 | self.train_patterns.append((X, Y)) 276 | 277 | 278 | def fit(self, X, Y): 279 | ''' 280 | Wrapper for train_pattern 281 | ''' 282 | for x, y in zip(X, Y): 283 | self.train_pattern(x, y) 284 | 285 | 286 | def predict(self, X): 287 | ''' 288 | Predict the class of the pattern X 289 | ''' 290 | classes = np.unique(self.classes) 291 | results = [] 292 | memberships = self.membership(X) 293 | max_prediction = 0 294 | pred_class = 0 295 | for _class in classes: 296 | mask = np.zeros((len(self.hyperboxes),)) 297 | mask[np.where(self.classes == _class)] = 1 298 | p = memberships * mask 299 | prediction, class_index = np.max(p), np.argmax(p) 300 | if prediction > max_prediction: 301 | max_prediction = prediction 302 | pred_class = class_index 303 | 304 | return max_prediction, self.classes[pred_class] 305 | 306 | 307 | def score(self, X, Y): 308 | ''' 309 | Scores the classifier 310 | ''' 311 | count = 0 312 | for x, y in zip(X, Y): 313 | _, pred = self.predict(x) 314 | if y == pred: 315 | count += 1 316 | 317 | return count / len(Y) 318 | 319 | 320 | def animate(self, frame_rate=10, filename='', verbose=True): 321 | ''' 322 | To make a video of the classifier training. 323 | NOTE: Only possible when working with 2 dimensional patterns 324 | ''' 325 | if self.isanimate: 326 | animator = Animator(box_history=self.box_history, 327 | train_patterns=self.train_patterns, 328 | classes=self.classes, 329 | frame_rate=frame_rate, 330 | exp_bound=self.exp_bound, 331 | sensitivity=self.sensitivity, 332 | filename=filename, 333 | verbose=verbose) 334 | 335 | animator.animate() 336 | 337 | return animator.filename 338 | 339 | else: 340 | raise Exception('No animation data was collected! Create a fuzzy classifier instance with animate=True') 341 | 342 | 343 | if __name__ == "__main__": 344 | 345 | patterns = np.array([[0.1, 0.1], 346 | [0.6, 0.6], 347 | [0.5, 0.5], 348 | [0.4, 0.3]]) 349 | 350 | classes = np.array([0, 1, 0, 1]) 351 | 352 | df = pd.read_csv('iris.data', header=None, names=['sepal length', 'sepal width', 'petal length', 'petal width', 'class']) 353 | 354 | df = df[~(df['class']=='Iris-virginica')] 355 | 356 | df.head() 357 | 358 | df.replace(to_replace='Iris-setosa', value=0, inplace=True) 359 | 360 | df.replace(to_replace='Iris-versicolor', value=1, inplace=True) 361 | 362 | df = df.sample(frac=1) 363 | 364 | X_train = df[['sepal length', 'petal length']].values 365 | Y_train = df['class'].values 366 | 367 | _max = np.max(X_train, axis=0) 368 | _min = np.min(X_train, axis=0) 369 | X_train = (X_train - _min) / (_max - _min) 370 | 371 | f = FuzzyMMC() 372 | f.fit(X_train, Y_train) 373 | print("Trained") 374 | --------------------------------------------------------------------------------