//  [python]
def train_gmm(self, components=20, iters=100, gmm_init_mode='rd', gmm_type='soft', prototype_path=None, log_space=False):

        # print('<<<<<<<<<<INITIALIZING>>>>>>>>>> \n means: {} \n sigma: {}\n, weights: {}'.format(gmm.means_, gmm.covariances_, gmm.weights_))
        assert gmm_init_mode in ['rd', 'fp', 'km']
        assert gmm_type in ['soft', 'hard']
        nc = pickle.load(open(TABLES_PREPROCESS / 'nc.dat', 'rb'))

        # we use fix random seed
        # random.seed(100)
        # unfold and shuffle nc data
        data = []
        for k, v in nc.items():
            if to_numeral(k) == None:
                print('invalid numeral {}'.format(k))
            else:
                data += [[to_numeral(k)]]*v

        print('total number of different numerals: ', len(nc))
        print('total number of numeral samples: ', len(data))

        # shuffle and subsample for MEM problem
        random.shuffle(data)

        if len(data) > 2000000:
            data = data[:2000000]

        if log_space:
            data = [weighted_log(x[0]) for x in data]

        print('subsampled to {}'.format(len(data)))

        data = np.array(data).reshape(-1,1)
        # getting initialization parameters
        if gmm_init_mode == 'km':

            if gmm_type == 'soft':
                gmm = mixture.GaussianMixture(components, max_iter=iters, n_init=1, verbose=10, init_params='kmeans')
            else:
                gmm = HardEMGaussianMixture(components, max_iter=iters, n_init=1, verbose=10, init_params='kmeans')


        else:
            # random select means
            if gmm_init_mode == 'rd' :
                prototypes = np.random.choice(data.reshape(-1), components)
            else:
                assert prototype_path is not None
                if log_space:
                    GMM_ = TABLES_REPR /  'gmm_log'
                else:
                    GMM_ = TABLES_REPR / 'gmm'

                prototypes = pickle.load(open(GMM_ / prototype_path, 'rb'))

                assert len(prototypes) == components

            mus = prototypes
            min_sigma = 1e-6

            diff = np.abs(data.reshape(len(data)) - mus[:, np.newaxis])

            amin = np.argmin(diff, axis=0)

            K = len(prototypes)
            clusters = [[0] for i in range(K)]
            for i in range(len(data)):
                clusters[amin[i]].append(data[i])

            means = np.array([np.mean(i) for i in clusters]).reshape(-1, 1)

            covs = np.array([np.std(i) if len(i) > 1 else min_sigma for i in clusters]).reshape(-1, 1, 1)
            precision = np.linalg.inv(covs)

            weights = np.array([len(c) for c in clusters])
            weights = weights / np.sum(weights)

            if gmm_type == 'soft':
                gmm = mixture.GaussianMixture(components, max_iter=iters, n_init=1, verbose=10, means_init=means,
                                  precisions_init=precision, weights_init=weights)
            else:
                gmm = HardEMGaussianMixture(components, max_iter=iters, n_init=1, verbose=10, means_init=means,
                                  precisions_init=precision, weights_init=weights)

        gmm.fit(data)
        if log_space:
            GMM_  = TABLES_REPR / 'gmm_log'
        else:
            GMM_  = TABLES_REPR / 'gmm'

        if not os.path.exists(GMM_):
            os.makedirs(GMM_)

        def single_variable_gaussian(x, mu, sigma):
            return 1. / (np.sqrt(2. * np.pi) * sigma) * np.exp(-np.power((x - mu) / sigma, 2.) / 2)

        def draw(gmm, X):
            x_min, x_max = min(X), max(X)
            # x = np.linspace(x_min, x_max, 10000)
            # x = np.array([])
            # for i in range(len(gmm.means_)):
            #     range_min, range_max = gmm.means_[i][0]-2 * gmm.covariances_[i][0], gmm.means_[i][0] + 2 * gmm.covariances_[i][0]
            #     x = np.append(x, np.linspace(range_min, range_max, 20))
            # x.sort()
            # print(x)
            print(gmm.means_)
            print(gmm.covariances_)
            print(gmm.weights_)

            X.sort()
            sum_y = np.zeros_like(X)
            plt.figure(0)
            plt.title('components')
            for i in range(len(gmm.means_)):
                y = single_variable_gaussian(X, gmm.means_[i][0], gmm.covariances_[i][0])
                y[y > 1] = 0 # set to 0 for better plot!
                sum_y += y * gmm.weights_[i]
                # yp = single_variable_gaussian(X, gmm.means_[i][0], gmm.covariances_[i][0])
                # yp[yp > 1] = 0
                # sum_yp += yp
                plt.plot(X, y)
            plt.savefig(GMM_ / f'components-{components}.png')

            plt.figure(1)
            plt.title('mixtures')

            plt.plot(X, sum_y, 'g-')
            plt.savefig(GMM_ / f'mixture-{components}.png')


        # 'rd' indicates for random initialization, 'fp' for 'from prototypes'

        pickle.dump(gmm, open(GMM_ / f'gmm-{components}-{gmm_init_mode}-{gmm_type}.dat'))
        print('means: {gmm.means_} \n sigma: {}\n, weights: {}'.format(gmm.means_, gmm.covariances_, gmm.weights_))

        if log_space:
            data_points = np.array([weighted_log(x) for x in np.array(list(nc.keys()), dtype=np.float32)]).reshape(-1,1)
        else:
            data_points = np.array(list(nc.keys()), dtype=np.float32).reshape(-1,1)

        posterior = gmm.predict_proba(data_points)
        path = GMM_ / f'gmm_posterior-{components}-{gmm_init_mode}-{gmm_type}.dat'
        pickle.dump(posterior, open(path, 'wb'))
        print('...Saving trained GMMs objects to {}'.format(path))