def remove_contained_hyperboxes(self):
        """
        Remove all hyperboxes contained in other hyperboxes
        """
        numBoxes = len(self.classId)
        indtokeep = np.ones(numBoxes, dtype=np.bool)

        for i in range(numBoxes):
            memValue = membership_gfmm(self.V[i], self.W[i], self.V, self.W,
                                       self.gamma, self.oper)
            isInclude = (self.classId[memValue == 1] == self.classId[i]).all()

            # memValue always has one value being 1 because of self-containing
            if np.sum(memValue == 1) > 1 and isInclude == True:
                indtokeep[i] = False

        self.V = self.V[indtokeep, :]
        self.W = self.W[indtokeep, :]
        self.classId = self.classId[indtokeep]
    def fit(self, X_l, X_u, patClassId):
        """
        Xl          Input data lower bounds (rows = objects, columns = features)
        Xu          Input data upper bounds (rows = objects, columns = features)
        patClassId  Input data class labels (crisp)
        """

        if self.isNorm == True:
            X_l, X_u = self.dataPreprocessing(X_l, X_u)

        time_start = time.perf_counter()

        self.V = X_l
        self.W = X_u
        self.classId = patClassId

        yX, xX = X_l.shape

        #        if len(self.cardin) == 0 or len(self.clusters) == 0:
        #            self.cardin = np.ones(yX)
        #            self.clusters = np.empty(yX, dtype=object)
        #            for i in range(yX):
        #                self.clusters[i] = np.array([i], dtype = np.int64)

        if self.isDraw:
            mark_col = np.array(['r', 'g', 'b', 'y', 'c', 'm', 'k'])
            drawing_canvas = self.initialize_canvas_graph("GFMM - AGGLO-2", xX)

            # plot initial hyperbox
            Vt, Wt = self.pcatransform()
            color_ = np.empty(len(self.classId), dtype=object)
            for c in range(len(self.classId)):
                color_[c] = mark_col[self.classId[c]]
            boxes = drawbox(Vt, Wt, drawing_canvas, color_)
            self.delay()
            hyperboxes = list(boxes)

        # training
        isTraining = True
        while isTraining:
            isTraining = False

            k = 0  # input pattern index
            while k < len(self.classId):
                if self.simil == 'short':
                    b = membership_gfmm(self.W[k], self.V[k], self.V, self.W,
                                        self.gamma, self.oper)
                elif self.simil == 'long':
                    b = membership_gfmm(self.V[k], self.W[k], self.W, self.V,
                                        self.gamma, self.oper)
                else:
                    b = asym_similarity_one_many(self.V[k], self.W[k], self.V,
                                                 self.W, self.gamma, self.sing,
                                                 self.oper)

                indB = np.argsort(b)[::-1]
                sortB = b[indB]

                maxB = sortB[sortB >=
                             self.bthres]  # apply membership threshold

                if len(maxB) > 0:
                    indmaxB = indB[sortB >= self.bthres]
                    # remove self-membership
                    maxB = maxB[indmaxB != k]
                    indmaxB = indmaxB[indmaxB != k]

                    # remove memberships to boxes from other classes
                    # idx_other_classes = np.where(np.logical_and(self.classId[indmaxB] != self.classId[k], self.classId[indmaxB] != 0))
                    #idx_same_classes = np.where(np.logical_or(self.classId[indmaxB] == self.classId[k], self.classId[indmaxB] == 0))
                    #idx_same_classes = np.where(self.classId[indmaxB] == self.classId[k])[0] # np.logical_or(self.classId[indmaxB] == self.classId[k], self.classId[indmaxB] == 0)

                    #maxB = np.delete(maxB, idx_other_classes)
                    idx_same_classes = np.logical_or(
                        self.classId[indmaxB] == self.classId[k],
                        self.classId[indmaxB] == 0)
                    maxB = maxB[idx_same_classes]
                    # leaving memeberships to unlabelled boxes
                    indmaxB = indmaxB[idx_same_classes]

                    #                    if len(maxB) > 30: # trim the set of memberships to speedup processing
                    #                        maxB = maxB[0:30]
                    #                        indmaxB = indmaxB[0:30]

                    pairewise_maxb = np.concatenate((np.minimum(
                        k, indmaxB)[:, np.newaxis], np.maximum(
                            k, indmaxB)[:, np.newaxis], maxB[:, np.newaxis]),
                                                    axis=1)

                    for i in range(pairewise_maxb.shape[0]):
                        # calculate new coordinates of k-th hyperbox by including pairewise_maxb(i,1)-th box, scrap the latter and leave the rest intact
                        # agglomorate pairewise_maxb(i, 0) and pairewise_maxb(i, 1) by adjusting pairewise_maxb(i, 0)
                        # remove pairewise_maxb(i, 1) by getting newV from 1 -> pairewise_maxb(i, 0) - 1, new coordinates for pairewise_maxb(i, 0), from pairewise_maxb(i, 0) + 1 -> pairewise_maxb(i, 1) - 1, pairewise_maxb(i, 1) + 1 -> end

                        # TODO: Improve it by change row pairewise_maxb[i, 0], after that using mask and mark pairewise_maxb[i, 1] = false => Slicing
                        row1 = int(pairewise_maxb[i, 0])
                        row2 = int(pairewise_maxb[i, 1])
                        newV = np.concatenate(
                            (self.V[:row1],
                             np.minimum(self.V[row1], self.V[row2]).reshape(
                                 1, -1), self.V[row1 + 1:row2],
                             self.V[row2 + 1:]),
                            axis=0)
                        newW = np.concatenate(
                            (self.W[:row1],
                             np.maximum(self.W[row1], self.W[row2]).reshape(
                                 1, -1), self.W[row1 + 1:row2],
                             self.W[row2 + 1:]),
                            axis=0)
                        newClassId = np.concatenate(
                            (self.classId[:row2], self.classId[row2 + 1:]))

                        #                        index_remain = np.ones(len(self.classId)).astype(np.bool)
                        #                        index_remain[row2] = False
                        #                        newV = self.V[index_remain]
                        #                        newW = self.W[index_remain]
                        #                        newClassId = self.classId[index_remain]
                        #                        if row1 < row2:
                        #                            tmp_row = row1
                        #                        else:
                        #                            tmp_row = row1 - 1
                        #                        newV[tmp_row] = np.minimum(self.V[row1], self.V[row2])
                        #                        newW[tmp_row] = np.maximum(self.W[row1], self.W[row2])

                        # adjust the hyperbox if no overlap and maximum hyperbox size is not violated
                        # position of adjustment is pairewise_maxb[i, 0] in new bounds
                        if (not is_overlap(newV, newW, int(
                                pairewise_maxb[i, 0]), newClassId)) and (
                                    ((newW[int(pairewise_maxb[i, 0])] -
                                      newV[int(pairewise_maxb[i, 0])]) <=
                                     self.teta).all() == True):
                            self.V = newV
                            self.W = newW
                            self.classId = newClassId

                            #                            self.cardin[int(pairewise_maxb[i, 0])] = self.cardin[int(pairewise_maxb[i, 0])] + self.cardin[int(pairewise_maxb[i, 1])]
                            #                            #self.cardin = np.delete(self.cardin, int(pairewise_maxb[i, 1]))
                            #                            self.cardin = np.append(self.cardin[0:int(pairewise_maxb[i, 1])], self.cardin[int(pairewise_maxb[i, 1]) + 1:])
                            #
                            #                            self.clusters[int(pairewise_maxb[i, 0])] = np.append(self.clusters[int(pairewise_maxb[i, 0])], self.clusters[int(pairewise_maxb[i, 1])])
                            #                            #self.clusters = np.delete(self.clusters, int(pairewise_maxb[i, 1]))
                            #                            self.clusters = np.append(self.clusters[0:int(pairewise_maxb[i, 1])], self.clusters[int(pairewise_maxb[i, 1]) + 1:])
                            #
                            isTraining = True

                            if k != pairewise_maxb[
                                    i,
                                    0]:  # position pairewise_maxb[i, 1] (also k) is removed, so next step should start from pairewise_maxb[i, 1]
                                k = k - 1

                            if self.isDraw:
                                try:
                                    hyperboxes[int(
                                        pairewise_maxb[i, 1])].remove()
                                    hyperboxes[int(
                                        pairewise_maxb[i, 0])].remove()
                                except:
                                    print("No remove old hyperbox")

                                Vt, Wt = self.pcatransform()

                                box_color = 'k'
                                if self.classId[int(
                                        pairewise_maxb[i, 0])] < len(mark_col):
                                    box_color = mark_col[self.classId[int(
                                        pairewise_maxb[i, 0])]]

                                box = drawbox(
                                    np.asmatrix(Vt[int(pairewise_maxb[i, 0])]),
                                    np.asmatrix(Wt[int(pairewise_maxb[i, 0])]),
                                    drawing_canvas, box_color)
                                self.delay()
                                hyperboxes[int(pairewise_maxb[i, 0])] = box[0]
                                hyperboxes.remove(hyperboxes[int(
                                    pairewise_maxb[i, 1])])

                            break  # if hyperbox adjusted there's no need to look at other hyperboxes

                k = k + 1

        time_end = time.perf_counter()
        self.elapsed_training_time = time_end - time_start

        return self
Ejemplo n.º 3
0
    def fit(self, X_l, X_u, patClassId):
        """
        Training the classifier
        
         Xl             Input data lower bounds (rows = objects, columns = features)
         Xu             Input data upper bounds (rows = objects, columns = features)
         patClassId     Input data class labels (crisp). patClassId[i] = 0 corresponds to an unlabeled item
        
        """
        print('--Online Learning--')

        if self.isNorm == True:
            X_l, X_u = self.dataPreprocessing(X_l, X_u)
        X_l = X_l.astype(np.float32)
        X_u = X_u.astype(np.float32)
        time_start = time.perf_counter()

        yX, xX = X_l.shape
        teta = self.teta

        mark = np.array([
            '*', 'o', 'x', '+', '.', ',', 'v', '^', '<', '>', '1', '2', '3',
            '4', '8', 's', 'p', 'P', 'h', 'H', 'X', 'D', '|', '_'
        ])
        mark_col = np.array(['r', 'g', 'b', 'y', 'c', 'm', 'k'])

        listLines = list()
        listInputSamplePoints = list()

        if self.isDraw:
            drawing_canvas = self.initialize_canvas_graph(
                "GFMM - Online learning", xX)

            if self.V.size > 0:
                # draw existed hyperboxes
                color_ = np.array(['k'] * len(self.classId), dtype=object)
                for c in range(len(self.classId)):
                    if self.classId[c] < len(mark_col):
                        color_[c] = mark_col[self.classId[c]]

                hyperboxes = drawbox(self.V[:, 0:np.minimum(xX, 3)],
                                     self.W[:, 0:np.minimum(xX, 3)],
                                     drawing_canvas, color_)
                listLines.extend(hyperboxes)
                self.delay()

        self.misclass = 1

        while self.misclass > 0 and teta >= self.tMin:
            # for each input sample
            for i in range(yX):
                classOfX = patClassId[i]
                # draw input samples
                if self.isDraw:
                    if i == 0 and len(listInputSamplePoints) > 0:
                        # reset input point drawing
                        for point in listInputSamplePoints:
                            point.remove()
                        listInputSamplePoints.clear()

                    color_ = 'k'
                    if classOfX < len(mark_col):
                        color_ = mark_col[classOfX]

                    if (X_l[i, :] == X_u[i, :]).all():
                        marker_ = 'd'
                        if classOfX < len(mark):
                            marker_ = mark[classOfX]

                        if xX == 2:
                            inputPoint = drawing_canvas.plot(X_l[i, 0],
                                                             X_l[i, 1],
                                                             color=color_,
                                                             marker=marker_)
                        else:
                            inputPoint = drawing_canvas.plot([X_l[i, 0]],
                                                             [X_l[i, 1]],
                                                             [X_l[i, 2]],
                                                             color=color_,
                                                             marker=marker_)

                        #listInputSamplePoints.append(inputPoint)
                    else:
                        inputPoint = drawbox(
                            np.asmatrix(X_l[i, 0:np.minimum(xX, 3)]),
                            np.asmatrix(X_u[i, 0:np.minimum(xX, 3)]),
                            drawing_canvas, color_)

                    listInputSamplePoints.append(inputPoint[0])
                    self.delay()

                if self.V.size == 0:  # no model provided - starting from scratch
                    self.V = np.array([X_l[0]])
                    self.W = np.array([X_u[0]])
                    self.classId = np.array([patClassId[0]])

                    if self.isDraw == True:
                        # draw hyperbox
                        box_color = 'k'
                        if patClassId[0] < len(mark_col):
                            box_color = mark_col[patClassId[0]]

                        hyperbox = drawbox(
                            np.asmatrix(self.V[0, 0:np.minimum(xX, 3)]),
                            np.asmatrix(self.W[0, 0:np.minimum(xX, 3)]),
                            drawing_canvas, box_color)
                        listLines.append(hyperbox[0])
                        self.delay()

                else:
                    b = membership_gfmm(X_l[i], X_u[i], self.V, self.W,
                                        self.gamma)

                    index = np.argsort(b)[::-1]
                    bSort = b[index]

                    if bSort[0] != 1 or (classOfX != self.classId[index[0]]
                                         and classOfX != 0):
                        adjust = False
                        for j in index:
                            # test violation of max hyperbox size and class labels
                            if (classOfX == self.classId[j]
                                    or self.classId[j] == 0 or classOfX
                                    == 0) and ((np.maximum(self.W[j], X_u[i]) -
                                                np.minimum(self.V[j], X_l[i]))
                                               <= teta).all() == True:
                                # adjust the j-th hyperbox
                                self.V[j] = np.minimum(self.V[j], X_l[i])
                                self.W[j] = np.maximum(self.W[j], X_u[i])
                                indOfWinner = j
                                adjust = True
                                if classOfX != 0 and self.classId[j] == 0:
                                    self.classId[j] = classOfX

                                if self.isDraw:
                                    # Handle drawing graph
                                    box_color = 'k'
                                    if self.classId[j] < len(mark_col):
                                        box_color = mark_col[self.classId[j]]

                                    try:
                                        listLines[j].remove()
                                    except:
                                        pass

                                    hyperbox = drawbox(
                                        np.asmatrix(
                                            self.V[j, 0:np.minimum(xX, 3)]),
                                        np.asmatrix(
                                            self.W[j, 0:np.minimum(xX, 3)]),
                                        drawing_canvas, box_color)
                                    listLines[j] = hyperbox[0]
                                    self.delay()

                                break

                        # if i-th sample did not fit into any existing box, create a new one
                        if not adjust:
                            self.V = np.concatenate(
                                (self.V, X_l[i].reshape(1, -1)), axis=0)
                            self.W = np.concatenate(
                                (self.W, X_u[i].reshape(1, -1)), axis=0)
                            self.classId = np.concatenate(
                                (self.classId, [classOfX]))

                            if self.isDraw:
                                # handle drawing graph
                                box_color = 'k'
                                if self.classId[-1] < len(mark_col):
                                    box_color = mark_col[self.classId[-1]]

                                hyperbox = drawbox(
                                    np.asmatrix(X_l[i, 0:np.minimum(xX, 3)]),
                                    np.asmatrix(X_u[i, 0:np.minimum(xX, 3)]),
                                    drawing_canvas, box_color)
                                listLines.append(hyperbox[0])
                                self.delay()

                        elif self.V.shape[0] > 1:
                            for ii in range(self.V.shape[0]):
                                if ii != indOfWinner and self.classId[
                                        ii] != self.classId[indOfWinner]:
                                    caseDim = hyperbox_overlap_test(
                                        self.V, self.W, indOfWinner,
                                        ii)  # overlap test

                                    if caseDim.size > 0:
                                        self.V, self.W = hyperbox_contraction(
                                            self.V, self.W, caseDim, ii,
                                            indOfWinner)
                                        if self.isDraw:
                                            # Handle graph drawing
                                            boxii_color = boxwin_color = 'k'
                                            if self.classId[ii] < len(
                                                    mark_col):
                                                boxii_color = mark_col[
                                                    self.classId[ii]]

                                            if self.classId[indOfWinner] < len(
                                                    mark_col):
                                                boxwin_color = mark_col[
                                                    self.classId[indOfWinner]]

                                            try:
                                                listLines[ii].remove()
                                                listLines[indOfWinner].remove()
                                            except:
                                                pass

                                            hyperboxes = drawbox(
                                                self.V[[ii, indOfWinner],
                                                       0:np.minimum(xX, 3)],
                                                self.W[[ii, indOfWinner],
                                                       0:np.minimum(xX, 3)],
                                                drawing_canvas,
                                                [boxii_color, boxwin_color])
                                            listLines[ii] = hyperboxes[0]
                                            listLines[
                                                indOfWinner] = hyperboxes[1]
                                            self.delay()

            teta = teta * 0.9
            if teta >= self.tMin:
                result = predict(self.V, self.W, self.classId, X_l, X_u,
                                 patClassId, self.gamma, self.oper)
                self.misclass = result.summis

        # Draw last result


#        if self.isDraw == True:
#            # Handle drawing graph
#            drawing_canvas.cla()
#            color_ = np.empty(len(self.classId), dtype = object)
#            for c in range(len(self.classId)):
#                color_[c] = mark_col[self.classId[c]]
#
#            drawbox(self.V[:, 0:np.minimum(xX, 3)], self.W[:, 0:np.minimum(xX, 3)], drawing_canvas, color_)
#            self.delay()
#
#        if self.isDraw:
#            plt.show()

        time_end = time.perf_counter()
        self.elapsed_training_time = time_end - time_start

        return self
    def fit(self, X_l, X_u, patClassId):
        """
        X_l          Input data lower bounds (rows = objects, columns = features)
        X_u          Input data upper bounds (rows = objects, columns = features)
        patClassId  Input data class labels (crisp)
        """
        
        if self.isNorm == True:
            X_l, X_u = self.dataPreprocessing(X_l, X_u)
            
        time_start = time.perf_counter()
         
        self.V = X_l
        self.W = X_u
        self.classId = patClassId
        
        yX, xX = X_l.shape
        
#        if len(self.cardin) == 0 or len(self.clusters) == 0:
#            self.cardin = np.ones(yX)
#            self.clusters = np.empty(yX, dtype=object)
#            for i in range(yX):
#                self.clusters[i] = np.array([i], dtype = np.int32)
        
        if self.isDraw:
            mark_col = np.array(['r', 'g', 'b', 'y', 'c', 'm', 'k'])
            drawing_canvas = self.initialize_canvas_graph("GFMM - AGGLO-SM-Fast version", xX)
                
            # plot initial hyperbox
            Vt, Wt = self.pcatransform()
            color_ = np.empty(len(self.classId), dtype = object)
            for c in range(len(self.classId)):
                color_[c] = mark_col[self.classId[c]]
            drawbox(Vt, Wt, drawing_canvas, color_)
            self.delay()
        
        # training
        isTraining = True
        while isTraining:
            isTraining = False
            
            # calculate class masks
            yX, xX = self.V.shape
            labList = np.unique(self.classId)[::-1]
            clMask = np.zeros(shape = (yX, len(labList)), dtype = np.bool)
            for i in range(len(labList)):
                clMask[:, i] = self.classId == labList[i]
        
        	# calculate pairwise memberships *ONLY* within each class (faster!)
            b = np.zeros(shape = (yX, yX))
            
            for i in range(len(labList)):
                Vi = self.V[clMask[:, i]] # get bounds of patterns with class label i
                Wi = self.W[clMask[:, i]]
                clSize = np.sum(clMask[:, i]) # get number of patterns of class i
                clIdxs = np.nonzero(clMask[:, i])[0] # get position of patterns with class label i in the training set
                
                if self.simil == 'short':
                    for j in range(clSize):
                        b[clIdxs[j], clIdxs] = membership_gfmm(Wi[j], Vi[j], Vi, Wi, self.gamma, self.oper)
                elif self.simil == 'long':
                    for j in range(clSize):
                        b[clIdxs[j], clIdxs] = membership_gfmm(Vi[j], Wi[j], Wi, Vi, self.gamma, self.oper)
                else:
                    for j in range(clSize):
                        b[clIdxs[j], clIdxs] = membership_gfmm(Vi[j], Wi[j], Vi, Wi, self.gamma, self.oper)
                
            if yX == 1:
                maxb = np.array([])
            else:
                maxb = self.split_similarity_maxtrix(b, self.sing, False)
                if len(maxb) > 0:
                    maxb = maxb[(maxb[:, 2] >= self.bthres), :]
                    
                    if len(maxb) > 0:
                        # sort maxb in the decending order following the last column
                        idx_smaxb = np.argsort(maxb[:, 2])[::-1]
                        maxb = np.hstack((maxb[idx_smaxb, 0].reshape(-1, 1), maxb[idx_smaxb, 1].reshape(-1, 1), maxb[idx_smaxb, 2].reshape(-1, 1)))
                        #maxb = maxb[idx_smaxb]
            
            while len(maxb) > 0:
                curmaxb = maxb[0, :] # current position handling
                
                # calculate new coordinates of curmaxb(0)-th hyperbox by including curmaxb(1)-th box, scrap the latter and leave the rest intact
                row1 = int(curmaxb[0])
                row2 = int(curmaxb[1])
                newV = np.concatenate((self.V[0:row1, :], np.minimum(self.V[row1, :], self.V[row2, :]).reshape(1, -1), self.V[row1 + 1:row2, :], self.V[row2 + 1:, :]), axis=0)
                newW = np.concatenate((self.W[0:row1, :], np.maximum(self.W[row1, :], self.W[row2, :]).reshape(1, -1), self.W[row1 + 1:row2, :], self.W[row2 + 1:, :]), axis=0)
                newClassId = np.concatenate((self.classId[0:row2], self.classId[row2 + 1:]))
                
#                index_remain = np.ones(len(self.classId)).astype(np.bool)
#                index_remain[row2] = False
#                newV = self.V[index_remain]
#                newW = self.W[index_remain]
#                newClassId = self.classId[index_remain]
#                if row1 < row2:
#                    tmp_row = row1
#                else:
#                    tmp_row = row1 - 1
#                newV[tmp_row] = np.minimum(self.V[row1], self.V[row2])
#                newW[tmp_row] = np.maximum(self.W[row1], self.W[row2])
                       
                
                # adjust the hyperbox if no overlap and maximum hyperbox size is not violated
                if (not is_overlap(newV, newW, int(curmaxb[0]), newClassId)) and (((newW[int(curmaxb[0])] - newV[int(curmaxb[0])]) <= self.teta).all() == True):
                    isTraining = True
                    self.V = newV
                    self.W = newW
                    self.classId = newClassId
                    
#                    self.cardin[int(curmaxb[0])] = self.cardin[int(curmaxb[0])] + self.cardin[int(curmaxb[1])]
#                    self.cardin = np.append(self.cardin[0:int(curmaxb[1])], self.cardin[int(curmaxb[1]) + 1:])
#                            
#                    self.clusters[int(curmaxb[0])] = np.append(self.clusters[int(curmaxb[0])], self.clusters[int(curmaxb[1])])
#                    self.clusters = np.append(self.clusters[0:int(curmaxb[1])], self.clusters[int(curmaxb[1]) + 1:])
#                    
                    # remove joined pair from the list as well as any pair with lower membership and consisting of any of joined boxes
                    mask = (maxb[:, 0] != int(curmaxb[0])) & (maxb[:, 1] != int(curmaxb[0])) & (maxb[:, 0] != int(curmaxb[1])) & (maxb[:, 1] != int(curmaxb[1])) & (maxb[:, 2] >= curmaxb[2])
                    maxb = maxb[mask, :]
                    
                    # update indexes to accomodate removed hyperbox
                    # indices of V and W larger than curmaxb(1,2) are decreased 1 by the element whithin the location curmaxb(1,2) was removed 
                    if len(maxb) > 0:
                        maxb[maxb[:, 0] > int(curmaxb[1]), 0] = maxb[maxb[:, 0] > int(curmaxb[1]), 0] - 1
                        maxb[maxb[:, 1] > int(curmaxb[1]), 1] = maxb[maxb[:, 1] > int(curmaxb[1]), 1] - 1
                            
                    if self.isDraw:
                        Vt, Wt = self.pcatransform()
                        color_ = np.empty(len(self.classId), dtype = object)
                        for c in range(len(self.classId)):
                            color_[c] = mark_col[self.classId[c]]
                        drawing_canvas.cla()
                        drawbox(Vt, Wt, drawing_canvas, color_)
                        self.delay()
                else:
                    maxb = maxb[1:, :]  # scrap examined pair from the list
        
        time_end = time.perf_counter()
        self.elapsed_training_time = time_end - time_start
         
        return self
Ejemplo n.º 5
0
def predict(V, W, classId, XlT, XuT, patClassIdTest, gama = 1, oper = 'min'):
    """
    GFMM classifier (test routine)

      result = predict(V,W,classId,XlT,XuT,patClassIdTest,gama,oper)

    INPUT
      V                 Tested model hyperbox lower bounds
      W                 Tested model hyperbox upper bounds
      classId	          Input data (hyperbox) class labels (crisp)
      XlT               Test data lower bounds (rows = objects, columns = features)
      XuT               Test data upper bounds (rows = objects, columns = features)
      patClassIdTest    Test data class labels (crisp)
      gama              Membership function slope (default: 1)
      oper              Membership calculation operation: 'min' or 'prod' (default: 'min')

   OUTPUT
      result           A object with Bunch datatype containing all results as follows:
                          + summis           Number of misclassified objects
                          + misclass         Binary error map
                          + sumamb           Number of objects with maximum membership in more than one class
                          + out              Soft class memberships
                          + mem              Hyperbox memberships

    """
	if len(XlT.shape) == 1:
        XlT = XlT.reshape(1, -1)
    if len(XuT.shape) == 1:
        XuT = XuT.reshape(1, -1)
		
    #initialization
    yX = XlT.shape[0]
    misclass = np.zeros(yX)
    classes = np.unique(classId)
    noClasses = classes.size
    ambiguity = np.zeros((yX, 1))
    mem = np.zeros((yX, V.shape[0]))
    out = np.zeros((yX, noClasses))

    # classifications
    for i in range(yX):
        mem[i, :] = membership_gfmm(XlT[i, :], XuT[i, :], V, W, gama, oper) # calculate memberships for all hyperboxes
        bmax = mem[i,:].max()	                                          # get max membership value
        maxVind = np.nonzero(mem[i,:] == bmax)[0]                         # get indexes of all hyperboxes with max membership

        for j in range(noClasses):
            out[i, j] = mem[i, classId == classes[j]].max()            # get max memberships for each class

        ambiguity[i, :] = np.sum(out[i, :] == bmax) 						  # number of different classes with max membership

        if bmax == 0:
            print('zero maximum membership value')                     # this is probably bad...

        if len(np.unique(classId[maxVind])) > 1:
            misclass[i] = True
        else:
            misclass[i] = ~(np.any(classId[maxVind] == patClassIdTest[i]) | (patClassIdTest[i] == 0))
            
    # results
    sumamb = np.sum(ambiguity[:, 0] > 1)
    summis = np.sum(misclass).astype(np.int64)

    result = Bunch(summis = summis, misclass = misclass, sumamb = sumamb, out = out, mem = mem)
    return result