Advertisement
Guest User

Untitled

a guest
Apr 1st, 2020
92
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.13 KB | None | 0 0
  1. """
  2. CSCC11 - Introduction to Machine Learning, Winter 2020, Assignment 3
  3. B. Chan, S. Wei, D. Fleet
  4.  
  5. ===========================================================
  6.  
  7. COMPLETE THIS TEXT BOX:
  8.  
  9. Student Name: Felix Chen
  10. Student number: 1003331628
  11. UtorID: chenfeli
  12.  
  13. I hereby certify that the work contained here is my own
  14.  
  15.  
  16. Felix Chen
  17. (sign with your name)
  18.  
  19. ===========================================================
  20. """
  21.  
  22. import numpy as np
  23.  
  24. from functools import partial
  25.  
  26. class GMM:
  27. def __init__(self, init_centers):
  28. """ This class represents the GMM model.
  29.  
  30. TODO: You will need to implement the methods of this class:
  31. - _e_step: ndarray, ndarray -> ndarray
  32. - _m_step: ndarray, ndarray -> None
  33.  
  34. Implementation description will be provided under each method.
  35.  
  36. For the following:
  37. - N: Number of samples.
  38. - D: Dimension of input features.
  39. - K: Number of Gaussians.
  40. NOTE: K > 1
  41.  
  42. Args:
  43. - init_centers (ndarray (shape: (K, D))): A KxD matrix consisting K D-dimensional centers, each for a Gaussian.
  44. """
  45. assert len(init_centers.shape) == 2, f"init_centers should be a KxD matrix. Got: {init_centers.shape}"
  46. (self.K, self.D) = init_centers.shape
  47. assert self.K > 1, f"There must be at least 2 clusters. Got: {self.K}"
  48.  
  49. # Shape: K x D
  50. self.centers = np.copy(init_centers)
  51.  
  52. # Shape: K x D x D
  53. self.covariances = np.tile(np.eye(self.D), reps=(self.K, 1, 1))
  54.  
  55. # Shape: K x 1
  56. self.mixture_proportions = np.ones(shape=(self.K, 1)) / self.K
  57.  
  58. def _e_step(self, train_X):
  59. """ This method performs the E-step of the EM algorithm.
  60.  
  61. Args:
  62. - train_X (ndarray (shape: (N, D))): A NxD matrix consisting N D-dimensional input data.
  63.  
  64. Output:
  65. - probability_matrix (ndarray (shape: (N, K))): A NxK matrix consisting N conditional probabilities of p(z_k|x_i) (i.e. the responsibilities).
  66. """
  67.  
  68. probability_matrix = np.empty(shape=(train_X.shape[0], self.K))
  69.  
  70. # ====================================================
  71. # TODO: Implement your solution within the box
  72. def b(i, h):
  73. """
  74. We use this form for numerical stability, such that our exponents dont underflow
  75. b = ln( likelihood * prior ) = 1/2 [ quadratic * ln(normalization) * -2ln(prior)]
  76.  
  77. for cluster h
  78. for input yi
  79. """
  80.  
  81. assert 0 <= h < self.K, "h needs to be valid label"
  82.  
  83. y = train_X[i]
  84.  
  85. mu = self.centers[h]
  86. C = self.covariances[h]
  87.  
  88. mix = self.mixture_proportions[h]
  89.  
  90. r = (y - mu).T @ np.linalg.inv(C) @ (y - mu)
  91. r += np.multiply(self.D, np.log(np.multiply(2, np.pi))) + np.log(np.linalg.det(C))
  92. r -= np.multiply(2, np.log(mix))
  93. r = np.divide(r, 2)
  94.  
  95. return r[0]
  96.  
  97. N, _ = train_X.shape
  98.  
  99. for i in range(N):
  100.  
  101. # Think notes might be wrong, we take right shift by MAX (b j) to avoid underflow....
  102. exponents = np.array([b(i, h) for h in range(self.K)])
  103. c = max(exponents)
  104. exponents = -exponents + c
  105.  
  106. eValue = np.power(np.e, exponents)
  107. normalizeFactor = np.sum(eValue)
  108.  
  109. posteriorsForI = np.divide(eValue, normalizeFactor)
  110.  
  111. probability_matrix[i] = posteriorsForI
  112.  
  113. # ====================================================
  114.  
  115. assert probability_matrix.shape == (train_X.shape[0], self.K), f"probability_matrix shape mismatch. Expected: {(train_X.shape[0], self.K)}. Got: {probability_matrix.shape}"
  116.  
  117. return probability_matrix
  118.  
  119. def _m_step(self, train_X, probability_matrix):
  120. """ This method performs the M-step of the EM algorithm.
  121.  
  122. NOTE: This method updates self.centers, self.covariances, and self.mixture_proportions
  123.  
  124. Args:
  125. - train_X (ndarray (shape: (N, D))): A NxD matrix consisting N D-dimensional input data.
  126. - probability_matrix (ndarray (shape: (N, K))): A NxK matrix consisting N conditional probabilities of p(z_k|x_i) (i.e. the responsibilities).
  127.  
  128. Output:
  129. - centers (ndarray (shape: (K, D))): A KxD matrix consisting K D-dimensional means for each Gaussian component.
  130. - covariances (ndarray (shape: (K, D, D))): A KxDxD tensor consisting K DxD covariance matrix for each Gaussian component.
  131. - mixture_proportions (ndarray (shape: (K, 1))): A K-column vector consistent the mixture proportion for each Gaussian component.
  132. """
  133.  
  134. # ====================================================
  135. # TODO: Implement your solution within the box
  136. N, _ = train_X.shape
  137.  
  138. # Shape: K x D
  139. centers = np.zeros((self.K, self.D))
  140. # Shape: K x D x D
  141. covariances = np.zeros((self.K, self.D, self.D))
  142. # Shape: K x 1
  143. mixture_proportions = np.zeros((self.K, 1))
  144.  
  145. for j in range(self.K):
  146. posteriorsForJ = probability_matrix[:,j:j+1]
  147. # We're going to use this 3 times..
  148. sumPosteriorsForJ = np.sum(posteriorsForJ)
  149.  
  150. mixture_proportions[j] = np.divide(sumPosteriorsForJ, N)
  151.  
  152. mu = np.sum(np.repeat(posteriorsForJ, self.D, axis=1) * train_X, axis = 0)
  153. mu = np.divide(mu, sumPosteriorsForJ)
  154. centers[j] = mu
  155.  
  156. # C = np.zeros((self.D, self.D))
  157. # for i in range(N):
  158. # C = np.add(C, posteriorsForJ[i] * (train_X[i:i+1].T - mu.T) @ (train_X[i:i+1].T - mu.T).T)
  159.  
  160. # print((train_X[0:1].T - mu.T) @ (train_X[0:1].T - mu.T).T)
  161.  
  162. # C = np.divide(C, sumPosteriorsForJ)
  163. # print(C)
  164.  
  165. meanCentered = train_X-mu
  166. outerProducts = meanCentered[:,:,None]*meanCentered[:,None,:]
  167. outerProducts = np.repeat(posteriorsForJ, self.D * self.D).reshape((N, self.D, self.D)) * outerProducts
  168. C = np.sum(outerProducts, axis = 0) / sumPosteriorsForJ
  169. covariances[j] = C
  170. # ====================================================
  171.  
  172. assert centers.shape == (self.K, self.D), f"centers shape mismatch. Expected: {(self.K, self.D)}. Got: {centers.shape}"
  173. assert covariances.shape == (self.K, self.D, self.D), f"covariances shape mismatch. Expected: {(self.K, self.D, self.D)}. Got: {covariances.shape}"
  174. assert mixture_proportions.shape == (self.K, 1), f"mixture_proportions shape mismatch. Expected: {(self.K, 1)}. Got: {mixture_proportions.shape}"
  175.  
  176. return centers, covariances, mixture_proportions
  177.  
  178. def train(self, train_X, max_iterations=1000):
  179. """ This method trains the GMM model using EM algorithm.
  180.  
  181. NOTE: This method updates self.centers, self.covariances, and self.mixture_proportions
  182.  
  183. Args:
  184. - train_X (ndarray (shape: (N, D))): A NxD matrix consisting N D-dimensional input data.
  185. - max_iterations (int): Maximum number of iterations.
  186.  
  187. Output:
  188. - labels (ndarray (shape: (N, 1))): A N-column vector consisting N labels of input data.
  189. """
  190. assert len(train_X.shape) == 2 and train_X.shape[1] == self.D, f"train_X should be a NxD matrix. Got: {train_X.shape}"
  191. assert max_iterations > 0, f"max_iterations must be positive. Got: {max_iterations}"
  192. N = train_X.shape[0]
  193.  
  194. e_step = partial(self._e_step, train_X=train_X)
  195. m_step = partial(self._m_step, train_X=train_X)
  196.  
  197. labels = np.empty(shape=(N, 1), dtype=np.long)
  198. for _ in range(max_iterations):
  199. old_labels = labels
  200. # E-Step
  201. probability_matrix = e_step()
  202.  
  203. # Reassign labels
  204. labels = np.argmax(probability_matrix, axis=1).reshape((N, 1))
  205.  
  206. # Check convergence
  207. if np.allclose(old_labels, labels):
  208. break
  209.  
  210. # M-Step
  211. self.centers, self.covariances, self.mixture_proportions = m_step(probability_matrix=probability_matrix)
  212.  
  213. return labels
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement