fcm.py
1 """!
2 
3 @brief Cluster analysis algorithm: Fuzzy C-Means
4 @details Implementation based on paper @cite book::pattern_recognition_with_fuzzy.
5 
6 @authors Andrei Novikov (pyclustering@yandex.ru)
7 @date 2014-2019
8 @copyright GNU Public License
9 
10 @cond GNU_PUBLIC_LICENSE
11  PyClustering is free software: you can redistribute it and/or modify
12  it under the terms of the GNU General Public License as published by
13  the Free Software Foundation, either version 3 of the License, or
14  (at your option) any later version.
15 
16  PyClustering is distributed in the hope that it will be useful,
17  but WITHOUT ANY WARRANTY; without even the implied warranty of
18  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  GNU General Public License for more details.
20 
21  You should have received a copy of the GNU General Public License
22  along with this program. If not, see <http://www.gnu.org/licenses/>.
23 @endcond
24 
25 """
26 
27 
28 import numpy
29 
30 import pyclustering.core.fcm_wrapper as wrapper
31 
32 from pyclustering.core.wrapper import ccore_library
33 
34 
35 class fcm:
36  """!
37  @brief Class represents Fuzzy C-means (FCM) clustering algorithm.
38  @details Fuzzy clustering is a form of clustering in which each data point can belong to more than one cluster.
39 
40  Fuzzy C-Means algorithm uses two general formulas for cluster analysis. The first is to updated membership of each
41  point:
42  \f[w_{ij}=\frac{1}{\sum_{k=0}^{c}\left ( \frac{\left \| x_{i}-c_{j} \right \|}{\left \| x_{i}-c_{k} \right \|} \right )^{\frac{2}{m-1}}}\f]
43 
44  The second formula is used to update centers in line with obtained centers:
45  \f[c_{k}=\frac{\sum_{i=0}^{N}w_{k}\left ( x_{i} \right )^{m}x_{i}}{\sum_{i=0}^{N}w_{k}\left ( x_{i} \right )^{m}}\f]
46 
47  Fuzzy C-Means clustering results depend on initial centers. Algorithm K-Means++ can used for center initialization
48  from module 'pyclustering.cluster.center_initializer'.
49 
50  CCORE implementation of the algorithm uses thread pool to parallelize the clustering process.
51 
52  Here is an example how to perform cluster analysis using Fuzzy C-Means algorithm:
53  @code
54  from pyclustering.samples.definitions import FAMOUS_SAMPLES
55  from pyclustering.cluster import cluster_visualizer
56  from pyclustering.cluster.center_initializer import kmeans_plusplus_initializer
57  from pyclustering.cluster.fcm import fcm
58  from pyclustering.utils import read_sample
59 
60  # load list of points for cluster analysis
61  sample = read_sample(FAMOUS_SAMPLES.SAMPLE_OLD_FAITHFUL)
62 
63  # initialize
64  initial_centers = kmeans_plusplus_initializer(sample, 2, kmeans_plusplus_initializer.FARTHEST_CENTER_CANDIDATE).initialize()
65 
66  # create instance of Fuzzy C-Means algorithm
67  fcm_instance = fcm(sample, initial_centers)
68 
69  # run cluster analysis and obtain results
70  fcm_instance.process()
71  clusters = fcm_instance.get_clusters()
72  centers = fcm_instance.get_centers()
73 
74  # visualize clustering results
75  visualizer = cluster_visualizer()
76  visualizer.append_clusters(clusters, sample)
77  visualizer.append_cluster(centers, marker='*', markersize=10)
78  visualizer.show()
79  @endcode
80 
81  The next example shows how to perform image segmentation using Fuzzy C-Means algorithm:
82  @code
83  from pyclustering.cluster.center_initializer import kmeans_plusplus_initializer
84  from pyclustering.cluster.fcm import fcm
85  from pyclustering.utils import read_image, draw_image_mask_segments
86 
87  # load list of points for cluster analysis
88  data = read_image("stpetersburg_admiral.jpg")
89 
90  # initialize
91  initial_centers = kmeans_plusplus_initializer(data, 3, kmeans_plusplus_initializer.FARTHEST_CENTER_CANDIDATE).initialize()
92 
93  # create instance of Fuzzy C-Means algorithm
94  fcm_instance = fcm(data, initial_centers)
95 
96  # run cluster analysis and obtain results
97  fcm_instance.process()
98  clusters = fcm_instance.get_clusters()
99 
100  # visualize segmentation results
101  draw_image_mask_segments("stpetersburg_admiral.jpg", clusters)
102  @endcode
103 
104  @image html fcm_segmentation_stpetersburg.png "Image segmentation using Fuzzy C-Means algorithm."
105 
106  """
107 
108  def __init__(self, data, initial_centers, **kwargs):
109  """!
110  @brief Initialize Fuzzy C-Means algorithm.
111 
112  @param[in] data (array_like): Input data that is presented as array of points (objects), each point should be represented by array_like data structure.
113  @param[in] initial_centers (array_like): Initial coordinates of centers of clusters that are represented by array_like data structure: [center1, center2, ...].
114  @param[in] **kwargs: Arbitrary keyword arguments (available arguments: 'tolerance', 'itermax', 'm').
115 
116  <b>Keyword Args:</b><br>
117  - ccore (bool): Defines should be CCORE library (C++ pyclustering library) used instead of Python code or not.
118  - tolerance (float): Stop condition: if maximum value of change of centers of clusters is less than tolerance then algorithm stops processing.
119  - itermax (uint): Maximum number of iterations that is used for clustering process (by default: 200).
120  - m (float): Hyper-parameter that controls how fuzzy the cluster will be. The higher it is, the fuzzier the cluster will be in the end.
121  This parameter should be greater than 1 (by default: 2).
122 
123  """
124 
125  self.__data = data
126  self.__clusters = []
127  self.__centers = initial_centers
128  self.__membership = []
129 
130  self.__tolerance = kwargs.get('tolerance', 0.001)
131  self.__itermax = kwargs.get('itermax', 200)
132  self.__m = kwargs.get('m', 2)
133 
134  self.__degree = 2.0 / (self.__m - 1)
135 
136  self.__ccore = kwargs.get('ccore', True)
137  if self.__ccore is True:
138  self.__ccore = ccore_library.workable()
139 
140 
141  def process(self):
142  """!
143  @brief Performs cluster analysis in line with Fuzzy C-Means algorithm.
144 
145  @see get_clusters()
146  @see get_centers()
147  @see get_membership()
148 
149  """
150  if self.__ccore is True:
151  self.__process_by_ccore()
152  else:
153  self.__process_by_python()
154 
155  return self
156 
157 
158  def get_clusters(self):
159  """!
160  @brief Returns allocated clusters that consists of points that most likely (in line with membership) belong to
161  these clusters.
162 
163  @remark Allocated clusters can be returned only after data processing (use method process()). Otherwise empty list is returned.
164 
165  @return (list) List of allocated clusters, each cluster contains indexes from input data.
166 
167  @see process()
168  @see get_centers()
169  @see get_membership()
170 
171  """
172  return self.__clusters
173 
174 
175  def get_centers(self):
176  """!
177  @brief Returns list of centers of allocated clusters.
178 
179  @return (array_like) Cluster centers.
180 
181  @see process()
182  @see get_clusters()
183  @see get_membership()
184 
185  """
186  return self.__centers
187 
188 
189  def get_membership(self):
190  """!
191  @brief Returns cluster membership (probability) for each point in data.
192 
193  @return (array_like) Membership for each point in format [[Px1(c1), Px1(c2), ...], [Px2(c1), Px2(c2), ...], ...],
194  where [Px1(c1), Px1(c2), ...] membership for point x1.
195 
196  @see process()
197  @see get_clusters()
198  @see get_centers()
199 
200  """
201  return self.__membership
202 
203 
204  def __process_by_ccore(self):
205  """!
206  @brief Performs cluster analysis using C/C++ implementation.
207 
208  """
209  result = wrapper.fcm_algorithm(self.__data, self.__centers, self.__m, self.__tolerance, self.__itermax)
210 
211  self.__clusters = result[wrapper.fcm_package_indexer.INDEX_CLUSTERS]
212  self.__centers = result[wrapper.fcm_package_indexer.INDEX_CENTERS]
213  self.__membership = result[wrapper.fcm_package_indexer.INDEX_MEMBERSHIP]
214 
215 
216  def __process_by_python(self):
217  """!
218  @brief Performs cluster analysis using Python implementation.
219 
220  """
221  self.__data = numpy.array(self.__data)
222  self.__centers = numpy.array(self.__centers)
223 
224  self.__membership = numpy.zeros((len(self.__data), len(self.__centers)))
225 
226  change = float('inf')
227  iteration = 0
228 
229  while change > self.__tolerance and iteration < self.__itermax:
230  self.__update_membership()
231  centers = self.__calculate_centers()
232  change = self.__calculate_changes(centers)
233 
234  self.__centers = centers
235  iteration += 1
236 
237  self.__extract_clusters()
238 
239 
240  def __calculate_centers(self):
241  """!
242  @brief Calculate center using membership of each cluster.
243 
244  @return (list) Updated clusters as list of clusters. Each cluster contains indexes of objects from data.
245 
246  @return (numpy.array) Updated centers.
247 
248  """
249  dimension = self.__data.shape[1]
250  centers = numpy.zeros((len(self.__centers), dimension))
251 
252  for i in range(len(self.__centers)):
253  # multiplication '@' requires python version 3.5
254  centers[i] = numpy.divide(self.__membership[:, i] @ self.__data, numpy.sum(self.__membership[:, i]))
255 
256  return centers
257 
258 
259  def __update_membership(self):
260  """!
261  @brief Update membership for each point in line with current cluster centers.
262 
263  """
264  data_difference = numpy.zeros((len(self.__centers), len(self.__data)))
265 
266  for i in range(len(self.__centers)):
267  data_difference[i] = numpy.sum(numpy.square(self.__data - self.__centers[i]), axis=1)
268 
269  for i in range(len(self.__data)):
270  for j in range(len(self.__centers)):
271  divider = sum([pow(data_difference[j][i] / data_difference[k][i], self.__degree) for k in range(len(self.__centers)) if data_difference[k][i] != 0.0])
272 
273  if divider != 0.0:
274  self.__membership[i][j] = 1.0 / divider
275  else:
276  self.__membership[i][j] = 1.0
277 
278 
279  def __calculate_changes(self, updated_centers):
280  """!
281  @brief Calculate changes between centers.
282 
283  @return (float) Maximum change between centers.
284 
285  """
286  changes = numpy.sum(numpy.square(self.__centers - updated_centers), axis=1).T
287  return numpy.max(changes)
288 
289 
290  def __extract_clusters(self):
291  self.__clusters = [[] for i in range(len(self.__centers))]
292  belongs = numpy.argmax(self.__membership, axis=1)
293 
294  for i in range(len(belongs)):
295  self.__clusters[belongs[i]].append(i)
def __update_membership(self)
Update membership for each point in line with current cluster centers.
Definition: fcm.py:259
def __extract_clusters(self)
Definition: fcm.py:290
def get_centers(self)
Returns list of centers of allocated clusters.
Definition: fcm.py:175
def process(self)
Performs cluster analysis in line with Fuzzy C-Means algorithm.
Definition: fcm.py:141
def __calculate_centers(self)
Calculate center using membership of each cluster.
Definition: fcm.py:240
def __calculate_changes(self, updated_centers)
Calculate changes between centers.
Definition: fcm.py:279
def get_clusters(self)
Returns allocated clusters that consists of points that most likely (in line with membership) belong ...
Definition: fcm.py:158
Class represents Fuzzy C-means (FCM) clustering algorithm.
Definition: fcm.py:35
def get_membership(self)
Returns cluster membership (probability) for each point in data.
Definition: fcm.py:189
def __init__(self, data, initial_centers, kwargs)
Initialize Fuzzy C-Means algorithm.
Definition: fcm.py:108
def __process_by_ccore(self)
Performs cluster analysis using C/C++ implementation.
Definition: fcm.py:204
def __process_by_python(self)
Performs cluster analysis using Python implementation.
Definition: fcm.py:216