CoolFace
Apppublic

BridgeAI-Lab/Sem-nCG

sourceHugging Faceupdated 2y agoView on Hugging Face
1likes
tests.py508 linesDownload Raw Back to root
1import unittest2 3import numpy as np4import torch5from sentence_transformers import SentenceTransformer6 7from .encoder_models import SBertEncoder, get_encoder, get_sbert_encoder8from .semncg import (9    RankedGains,10    compute_cosine_similarity,11    compute_gain,12    score_ncg,13    compute_ncg,14    _validate_input_format,15    SemNCG16)17from .utils import (18    get_gpu,19    slice_embeddings,20    is_nested_list_of_type,21    flatten_list,22    prep_sentences,23    tokenize_and_prep_document24)25 26 27class TestUtils(unittest.TestCase):28    def test_get_gpu(self):29        gpu_count = torch.cuda.device_count()30        gpu_available = torch.cuda.is_available()31 32        # Test single boolean input33        self.assertEqual(get_gpu(True), 0 if gpu_available else "cpu")34        self.assertEqual(get_gpu(False), "cpu")35 36        # Test single string input37        self.assertEqual(get_gpu("cpu"), "cpu")38        self.assertEqual(get_gpu("gpu"), 0 if gpu_available else "cpu")39        self.assertEqual(get_gpu("cuda"), 0 if gpu_available else "cpu")40 41        # Test single integer input42        self.assertEqual(get_gpu(0), 0 if gpu_available else "cpu")43        self.assertEqual(get_gpu(1), 1 if gpu_available else "cpu")44 45        # Test list input with unique elements46        self.assertEqual(get_gpu([True, "cpu", 0]), [0, "cpu"] if gpu_available else ["cpu", "cpu", "cpu"])47 48        # Test list input with duplicate elements49        self.assertEqual(get_gpu([0, 0, "gpu"]), 0 if gpu_available else ["cpu", "cpu", "cpu"])50 51        # Test list input with duplicate elements of different types52        self.assertEqual(get_gpu([True, 0, "gpu"]), 0 if gpu_available else ["cpu", "cpu", "cpu"])53 54        # Test list input but only one element55        self.assertEqual(get_gpu([True]), 0 if gpu_available else "cpu")56 57        # Test list input with all integers58        self.assertEqual(get_gpu(list(range(gpu_count))),59                         list(range(gpu_count)) if gpu_available else gpu_count * ["cpu"])60 61        with self.assertRaises(ValueError):62            get_gpu("invalid")63 64        with self.assertRaises(ValueError):65            get_gpu(torch.cuda.device_count())66 67    def test_prep_sentences(self):68        # Test normal case69        self.assertEqual(prep_sentences(["Hello, world!", " This is a test. ", "!!!"]),70                         ['Hello, world!', 'This is a test.'])71 72        # Test case with only punctuations73        with self.assertRaises(ValueError):74            prep_sentences(["!!!", "..."])75 76        # Test case with empty list77        with self.assertRaises(ValueError):78            prep_sentences([])79 80    def test_tokenize_and_prep_document(self):81        # Test tokenize=True with string input82        self.assertEqual(tokenize_and_prep_document("Hello, world! This is a test.", True),83                         ['Hello, world!', 'This is a test.'])84 85        # Test tokenize=False with list of strings input86        self.assertEqual(tokenize_and_prep_document(["Hello, world!", "This is a test."], False),87                         ['Hello, world!', 'This is a test.'])88 89        # Test tokenize=True with empty document90        with self.assertRaises(ValueError):91            tokenize_and_prep_document("!!! ...", True)92 93    def test_slice_embeddings(self):94        # Case 195        embeddings = np.random.rand(10, 5)96        num_sentences = [3, 2, 5]97        expected_output = [embeddings[:3], embeddings[3:5], embeddings[5:]]98        self.assertTrue(99            all(np.array_equal(a, b) for a, b in zip(slice_embeddings(embeddings, num_sentences),100                                                     expected_output))101        )102 103        # Case 2104        num_sentences_nested = [[2, 1], [3, 4]]105        expected_output_nested = [[embeddings[:2], embeddings[2:3]], [embeddings[3:6], embeddings[6:]]]106        self.assertTrue(107            slice_embeddings(embeddings, num_sentences_nested), expected_output_nested108        )109 110        # Case 3111        document_sentences_count = [10, 8, 7]112        reference_sentences_count = [5, 3, 2]113        pred_sentences_count = [2, 2, 1]114        all_embeddings = np.random.rand(115            sum(document_sentences_count + reference_sentences_count + pred_sentences_count), 5,116        )117 118        embeddings = all_embeddings119        expected_doc_embeddings = [embeddings[:10], embeddings[10:18], embeddings[18:25]]120 121        embeddings = all_embeddings[25:]122        expected_ref_embeddings = [embeddings[:5], embeddings[5:8], embeddings[8:10]]123 124        embeddings = all_embeddings[35:]125        expected_pred_embeddings = [embeddings[:2], embeddings[2:4], embeddings[4:5]]126 127        doc_embeddings = slice_embeddings(all_embeddings, document_sentences_count)128        ref_embeddings = slice_embeddings(all_embeddings[sum(document_sentences_count):], reference_sentences_count)129        pred_embeddings = slice_embeddings(130            all_embeddings[sum(document_sentences_count + reference_sentences_count):], pred_sentences_count131        )132 133        self.assertTrue(doc_embeddings, expected_doc_embeddings)134        self.assertTrue(ref_embeddings, expected_ref_embeddings)135        self.assertTrue(pred_embeddings, expected_pred_embeddings)136 137        with self.assertRaises(TypeError):138            slice_embeddings(embeddings, "invalid")139 140    def test_is_nested_list_of_type(self):141        # Test case: Depth 0, single element matching element_type142        self.assertEqual(is_nested_list_of_type("test", str, 0), (True, ""))143 144        # Test case: Depth 0, single element not matching element_type145        is_valid, err_msg = is_nested_list_of_type("test", int, 0)146        self.assertEqual(is_valid, False)147 148        # Test case: Depth 1, list of elements matching element_type149        self.assertEqual(is_nested_list_of_type(["apple", "banana"], str, 1), (True, ""))150 151        # Test case: Depth 1, list of elements not matching element_type152        is_valid, err_msg = is_nested_list_of_type([1, 2, 3], str, 1)153        self.assertEqual(is_valid, False)154 155        # Test case: Depth 0 (Wrong), list of elements matching element_type156        is_valid, err_msg = is_nested_list_of_type([1, 2, 3], str, 0)157        self.assertEqual(is_valid, False)158 159        # Depth 2160        self.assertEqual(is_nested_list_of_type([[1, 2], [3, 4]], int, 2), (True, ""))161        self.assertEqual(is_nested_list_of_type([['1', '2'], ['3', '4']], str, 2), (True, ""))162        is_valid, err_msg = is_nested_list_of_type([[1, 2], ["a", "b"]], int, 2)163        self.assertEqual(is_valid, False)164 165        # Depth 3166        is_valid, err_msg = is_nested_list_of_type([[[1], [2]], [[3], [4]]], list, 3)167        self.assertEqual(is_valid, False)168        self.assertEqual(is_nested_list_of_type([[[1], [2]], [[3], [4]]], int, 3), (True, ""))169 170        # Test case: Depth is negative, expecting ValueError171        with self.assertRaises(ValueError):172            is_nested_list_of_type([1, 2], int, -1)173 174    def test_flatten_list(self):175        self.assertEqual(flatten_list([1, [2, 3], [[4], 5]]), [1, 2, 3, 4, 5])176        self.assertEqual(flatten_list([]), [])177        self.assertEqual(flatten_list([1, 2, 3]), [1, 2, 3])178        self.assertEqual(flatten_list([[[[1]]]]), [1])179 180 181class TestSBertEncoder(unittest.TestCase):182 183    def setUp(self) -> None:184        # Set up a test SentenceTransformer model185        self.model_name = "paraphrase-distilroberta-base-v1"186        self.sbert_model = get_sbert_encoder(self.model_name)187        self.device = "cpu"  # For testing on CPU188        self.batch_size = 32189        self.verbose = False190        self.encoder = SBertEncoder(self.sbert_model, self.device, self.batch_size, self.verbose)191 192    def test_encode_single_sentence(self):193        sentence = "Hello, world!"194        embeddings = self.encoder.encode([sentence])195        self.assertEqual(embeddings.shape, (1, 768))  # Adjust shape based on your model's embedding dimension196 197    def test_encode_multiple_sentences(self):198        sentences = ["Hello, world!", "This is a test."]199        embeddings = self.encoder.encode(sentences)200        self.assertEqual(embeddings.shape, (2, 768))  # Adjust shape based on your model's embedding dimension201 202    def test_get_sbert_encoder(self):203        model_name = "paraphrase-distilroberta-base-v1"204        sbert_model = get_sbert_encoder(model_name)205        self.assertIsInstance(sbert_model, SentenceTransformer)206 207    def test_encode_with_gpu(self):208        if torch.cuda.is_available():209            device = "cuda"210            encoder = get_encoder(self.sbert_model, device, self.batch_size, self.verbose)211            sentences = ["Hello, world!", "This is a test."]212            embeddings = encoder.encode(sentences)213            self.assertEqual(embeddings.shape, (2, 768))  # Adjust shape based on your model's embedding dimension214        else:215            self.skipTest("CUDA not available, skipping GPU test.")216 217    def test_encode_multi_device(self):218        if torch.cuda.device_count() < 2:219            self.skipTest("Multi-GPU test requires at least 2 GPUs.")220        else:221            devices = ["cuda:0", "cuda:1"]222            encoder = get_encoder(self.sbert_model, devices, self.batch_size, self.verbose)223            sentences = ["This is a test sentence.", "Here is another sentence.", "This is a test sentence."]224            embeddings = encoder.encode(sentences)225            self.assertIsInstance(embeddings, np.ndarray)226            self.assertEqual(embeddings.shape[0], 3)227            self.assertEqual(embeddings.shape[1], self.encoder.model.get_sentence_embedding_dimension())228 229 230class TestGetEncoder(unittest.TestCase):231    def setUp(self):232        self.device = "cuda" if torch.cuda.is_available() else "cpu"233        self.batch_size = 8234        self.verbose = False235 236    def _base_test(self, model_name):237        sbert_model = get_sbert_encoder(model_name)238        encoder = get_encoder(sbert_model, self.device, self.batch_size, self.verbose)239 240        # Assert241        self.assertIsInstance(encoder, SBertEncoder)242        self.assertEqual(encoder.device, self.device)243        self.assertEqual(encoder.batch_size, self.batch_size)244        self.assertEqual(encoder.verbose, self.verbose)245 246    def test_get_sbert_encoder(self):247        model_name = "stsb-roberta-large"248        self._base_test(model_name)249 250    def test_sbert_model(self):251        model_name = "all-mpnet-base-v2"252        self._base_test(model_name)253 254    def test_huggingface_model(self):255        """Test Huggingface models which work with SBert library"""256        model_name = "roberta-base"257        self._base_test(model_name)258 259    def test_get_encoder_environment_error(self):  # This parameter is used when using patch decorator260        model_name = "abc"  # Wrong model_name261        with self.assertRaises(EnvironmentError):262            get_sbert_encoder(model_name)263 264    def test_get_encoder_other_exception(self):265        model_name = "apple/OpenELM-270M"  # This model is not supported by SentenceTransformer lib266        with self.assertRaises(RuntimeError):267            get_sbert_encoder(model_name)268 269 270class TestRankedGainsDataclass(unittest.TestCase):271    def test_ranked_gains_dataclass(self):272        # Test initialization and attribute access273        gt_gains = [("doc1", 0.8), ("doc2", 0.6)]274        pred_gains = [("doc2", 0.7), ("doc1", 0.5)]275        k = 2276        ncg = 0.75277        ranked_gains = RankedGains(gt_gains, pred_gains, k, ncg)278 279        self.assertEqual(ranked_gains.gt_gains, gt_gains)280        self.assertEqual(ranked_gains.pred_gains, pred_gains)281        self.assertEqual(ranked_gains.k, k)282        self.assertEqual(ranked_gains.ncg, ncg)283 284 285class TestComputeCosineSimilarity(unittest.TestCase):286    def test_compute_cosine_similarity(self):287        doc_embeds = np.array([[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]])288        ref_embeds = np.array([[0.2, 0.3, 0.4], [0.5, 0.6, 0.7]])289        # Test compute_cosine_similarity function290        similarity_scores = compute_cosine_similarity(doc_embeds, ref_embeds)291        print(similarity_scores)292 293        # Example values, change as per actual function output294        expected_scores = [0.980, 0.997]295 296        self.assertAlmostEqual(similarity_scores[0], expected_scores[0], places=3)297        self.assertAlmostEqual(similarity_scores[1], expected_scores[1], places=3)298 299 300class TestComputeGain(unittest.TestCase):301    def test_compute_gain(self):302        # Test compute_gain function303        sim_scores = [0.8, 0.6, 0.7]304        gains = compute_gain(sim_scores)305        print(gains)306 307        # Example values, change as per actual function output308        expected_gains = [(0, 0.5), (2, 0.3333333333333333), (1, 0.16666666666666666)]309 310        self.assertEqual(gains, expected_gains)311 312 313class TestScoreNcg(unittest.TestCase):314    def test_score_ncg(self):315        # Test score_ncg function316        model_relevance = [0.8, 0.7, 0.6]317        gt_relevance = [1.0, 0.9, 0.8]318        ncg_score = score_ncg(model_relevance, gt_relevance)319        expected_ncg = 0.778  # Example value, change as per actual function output320 321        self.assertAlmostEqual(ncg_score, expected_ncg, places=3)322 323 324class TestComputeNcg(unittest.TestCase):325    def test_compute_ncg(self):326        # Test compute_ncg function327        pred_gains = [(0, 0.8), (2, 0.7), (1, 0.6)]328        gt_gains = [(0, 1.0), (1, 0.9), (2, 0.8)]329        k = 3330        ncg_score = compute_ncg(pred_gains, gt_gains, k)331        expected_ncg = 1.0  # TODO: Confirm this with Dr. Santu332 333        self.assertAlmostEqual(ncg_score, expected_ncg, places=6)334 335 336class TestValidateInputFormat(unittest.TestCase):337    def test_validate_input_format(self):338        # Test _validate_input_format function339        tokenize_sentences = True340        predictions = ["Prediction 1", "Prediction 2"]341        references = ["Reference 1", "Reference 2"]342        documents = ["Document 1", "Document 2"]343 344        # No exception should be raised for valid input345        try:346            _validate_input_format(tokenize_sentences, predictions, references, documents)347        except ValueError as e:348            self.fail(f"_validate_input_format raised ValueError unexpectedly: {str(e)}")349 350        # Test invalid input format351        predictions_invalid = [["Sentence 1 in prediction 1.", "Sentence 2 in prediction 1."],352                               ["Sentence 1 in prediction 2.", "Sentence 2 in prediction 2."]]353        references_invalid = [["Sentences in reference 1."], ["Sentences in reference 2."]]354        documents_invalid = [["Sentence 1 in document 1.", "Sentence 2 in document 1."],355                             ["Sentence 1 in document 2.", "Sentence 2 in document 2."]]356 357        with self.assertRaises(ValueError):358            _validate_input_format(tokenize_sentences, predictions_invalid, references, documents)359 360        with self.assertRaises(ValueError):361            _validate_input_format(tokenize_sentences, predictions, references_invalid, documents)362 363        with self.assertRaises(ValueError):364            _validate_input_format(tokenize_sentences, predictions, references, documents_invalid)365 366 367class TestSemNCG(unittest.TestCase):368    def setUp(self):369        self.model_name = "stsb-distilbert-base"370        self.metric = SemNCG(self.model_name)371 372    def _basic_assertion(self, result, debug: bool = False):373        self.assertIsInstance(result, tuple)374        self.assertEqual(len(result), 2)375        self.assertIsInstance(result[0], float)376        self.assertTrue(0.0 <= result[0] <= 1.0)377        self.assertIsInstance(result[1], list)378        if debug:379            for ranked_gain in result[1]:380                self.assertTrue(isinstance(ranked_gain, RankedGains))381                self.assertTrue(0.0 <= ranked_gain.ncg <= 1.0)382        else:383            for gain in result[1]:384                self.assertTrue(isinstance(gain, float))385                self.assertTrue(0.0 <= gain <= 1.0)386 387    def test_compute_basic(self):388        predictions = ["The cat sat on the mat.", "The quick brown fox jumps over the lazy dog."]389        references = ["A cat was sitting on a mat.", "A quick brown fox jumped over a lazy dog."]390        documents = ["There was a cat on a mat.", "The quick brown fox jumped over the lazy dog."]391 392        result = self.metric.compute(predictions=predictions, references=references, documents=documents)393        self._basic_assertion(result)394 395    def test_compute_with_tokenization(self):396        predictions = [["The cat sat on the mat."], ["The quick brown fox jumps over the lazy dog."]]397        references = [["A cat was sitting on a mat."], ["A quick brown fox jumped over a lazy dog."]]398        documents = [["There was a cat on a mat."], ["The quick brown fox jumped over the lazy dog."]]399 400        result = self.metric.compute(401            predictions=predictions, references=references, documents=documents, tokenize_sentences=False402        )403        self._basic_assertion(result)404 405    def test_compute_with_pre_compute_embeddings(self):406        predictions = ["The cat sat on the mat.", "The quick brown fox jumps over the lazy dog."]407        references = ["A cat was sitting on a mat.", "A quick brown fox jumped over a lazy dog."]408        documents = ["There was a cat on a mat.", "The quick brown fox jumped over the lazy dog."]409 410        result = self.metric.compute(411            predictions=predictions, references=references, documents=documents, pre_compute_embeddings=True412        )413        self._basic_assertion(result)414 415    def test_compute_with_debug(self):416        predictions = ["The cat sat on the mat.", "The quick brown fox jumps over the lazy dog."]417        references = ["A cat was sitting on a mat.", "A quick brown fox jumped over a lazy dog."]418        documents = ["There was a cat on a mat.", "The quick brown fox jumped over the lazy dog."]419 420        result = self.metric.compute(421            predictions=predictions, references=references, documents=documents, debug=True422        )423        self._basic_assertion(result, debug=True)424 425    def test_compute_invalid_input_format(self):426        predictions = "The cat sat on the mat."427        references = ["A cat was sitting on a mat."]428        documents = ["There was a cat on a mat."]429 430        with self.assertRaises(ValueError):431            self.metric.compute(predictions=predictions, references=references, documents=documents)432 433    def test_bad_inputs(self):434        def _call_metric(preds, refs, docs, tok):435            with self.assertRaises(Exception) as ctx:436                _ = self.metric.compute(437                    predictions=preds,438                    references=refs,439                    documents=docs,440                    tokenize_sentences=tok,441                    pre_compute_embeddings=True,442                )443            print(f"Raised Exception with message: {ctx.exception}")444            return ""445 446        # None Inputs447        # Case I448        tokenize_sentences = True449        predictions = [None]450        references = ["A cat was sitting on a mat."]451        documents = ["There was a cat on a mat."]452        print(f"Case I\n{_call_metric(predictions, references, documents, tokenize_sentences)}\n")453 454        # Case II455        tokenize_sentences = False456        predictions = [["A cat was sitting on a mat.", None]]457        references = [["A cat was sitting on a mat.", "A cat was sitting on a mat."]]458        documents = [["There was a cat on a mat.", "There was a cat on a mat."]]459        print(f"Case II\n{_call_metric(predictions, references, documents, tokenize_sentences)}\n")460 461        # Empty Input462        tokenize_sentences = True463        predictions = []464        references = ["A cat was sitting on a mat."]465        documents = ["There was a cat on a mat."]466        print(f"Case: Empty Input\n{_call_metric(predictions, references, documents, tokenize_sentences)}\n")467 468        # Empty String Input469        tokenize_sentences = True470        predictions = [""]471        references = ["A cat was sitting on a mat."]472        documents = ["There was a cat on a mat."]473        print(f"Case: Empty String Input\n{_call_metric(predictions, references, documents, tokenize_sentences)}\n")474 475    def _test_check_verbose(self):476        """UNUSED: previously used to manually check the progress bar477 478        This test should not be used since they rely on files that are479        not kept in version control. this is purely just left here for480        historical purposes and has the '_' prepended to the function481        name to avoid being executed.482        """483        import sqlite3484        import string485 486        con = sqlite3.connect('sem_ncg_samples.db')487        cur = con.cursor()488        data = cur.execute(489            'SELECT * FROM sem_ncg_samples').fetchmany(100)490        data = list(filter(491            lambda x: x[0].translate(492                str.maketrans('', '', string.punctuation)493                ).strip() != '',494            data495        ))496        preds, refs, docs = list(zip(*data))497        result = self.metric.compute(498            predictions=preds, references=refs, 499            documents=docs, verbose=True,500            gpu=2501        )502        503        breakpoint()504 505 506if __name__ == '__main__':507    unittest.main(verbosity=2)508