File size: 6,441 Bytes
68e781f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
"""Tests for cortex features 4-7: multimodal, meta-learning, dual memory, analogies."""

import pytest
import numpy as np

from palimseste.lm import PalimpsesteForCausalLM, PalimpsesteConfig
from palimseste.cortex import (
    MultimodalFusion, MetaLearner, DualMemory, Analogizer,
    ModalityBinding, MetaLearningResult, MemoryRecord, AnalogyResult,
)
from palimseste.hv_word2vec import HVWord2Vec, Word2VecConfig


def _build_model(D=3000, ctx=64, radius=100):
    cfg = PalimpsesteConfig(D=D, context_window=ctx, kernel_radius=radius, temperature=0.0)
    lm = PalimpsesteForCausalLM(config=cfg)
    lm.build_tokenizer("hello world test memory image text analogical reasoning python france")
    return lm


# ================================================================ MULTIMODAL
class TestMultimodalFusion:
    def test_learn_image(self):
        lm = _build_model()
        fusion = MultimodalFusion(mem=lm.mem, encoder=lm.encoder)
        img = np.random.randint(0, 255, (16, 16, 3), dtype=np.uint8)
        binding = fusion.learn_image("a red square", img)
        assert isinstance(binding, ModalityBinding)
        assert fusion.n_bindings == 1

    def test_find_image(self):
        lm = _build_model()
        fusion = MultimodalFusion(mem=lm.mem, encoder=lm.encoder)
        img = np.random.randint(0, 255, (16, 16, 3), dtype=np.uint8)
        fusion.learn_image("a red square", img)
        results = fusion.find_image("red")
        assert len(results) > 0
        assert "red square" in results[0][0]

    def test_describe_image(self):
        lm = _build_model()
        fusion = MultimodalFusion(mem=lm.mem, encoder=lm.encoder)
        img = np.random.randint(0, 255, (16, 16, 3), dtype=np.uint8)
        fusion.learn_image("a blue circle", img)
        results = fusion.describe_image(img)
        assert len(results) > 0
        # The exact image should match at sim ~1.0
        assert results[0][1] > 0.9

    def test_multiple_bindings(self):
        lm = _build_model()
        fusion = MultimodalFusion(mem=lm.mem, encoder=lm.encoder)
        for label in ["cat", "dog", "bird"]:
            img = np.random.randint(0, 255, (16, 16, 3), dtype=np.uint8)
            fusion.learn_image(label, img)
        assert fusion.n_bindings == 3


# ================================================================ META-LEARNING
class TestMetaLearner:
    def test_adapt(self):
        lm = _build_model()
        # Train some data so there's a replay set
        lm.train_on_qa_pairs([("hello", "hi there"), ("what is x", "x is y")])
        meta = MetaLearner(mem=lm.mem, phi=lm.phi)
        result = meta.adapt()
        assert isinstance(result, MetaLearningResult)

    def test_current_config(self):
        lm = _build_model()
        meta = MetaLearner(mem=lm.mem, phi=lm.phi)
        cfg = meta.current_config
        assert "radius" in cfg
        assert "min_weight" in cfg

    def test_n_adaptations(self):
        lm = _build_model()
        meta = MetaLearner(mem=lm.mem, phi=lm.phi)
        assert meta.n_adaptations >= 0


# ================================================================ DUAL MEMORY
class TestDualMemory:
    def test_store_episodic(self):
        lm = _build_model()
        dm = DualMemory(mem=lm.mem, encoder=lm.encoder)
        record = dm.store_episodic("user said hello")
        assert record.memory_type == "episodic"
        assert dm.n_episodic == 1

    def test_store_semantic(self):
        lm = _build_model()
        dm = DualMemory(mem=lm.mem, encoder=lm.encoder)
        record = dm.store_semantic("the sky is blue")
        assert record.memory_type == "semantic"
        assert dm.n_semantic == 1

    def test_recall(self):
        lm = _build_model()
        dm = DualMemory(mem=lm.mem, encoder=lm.encoder)
        dm.store_semantic("python is a programming language")
        dm.store_episodic("user asked about python")
        results = dm.recall("python", top_k=3)
        assert len(results) > 0
        assert all(isinstance(r[0], MemoryRecord) for r in results)

    def test_forget_old(self):
        lm = _build_model()
        dm = DualMemory(mem=lm.mem, encoder=lm.encoder)
        dm.store_episodic("old memory")
        dm.store_semantic("persistent fact")
        # max_age=0 removes all episodic
        n = dm.forget_old_episodic(max_age=0)
        assert n >= 1
        assert dm.n_episodic == 0
        assert dm.n_semantic == 1

    def test_counts(self):
        lm = _build_model()
        dm = DualMemory(mem=lm.mem, encoder=lm.encoder)
        dm.store_episodic("e1")
        dm.store_episodic("e2")
        dm.store_semantic("s1")
        assert dm.n_episodic == 2
        assert dm.n_semantic == 1
        assert dm.total == 3


# ================================================================ ANALOGIES
class TestAnalogizer:
    def _build_w2v(self):
        w2v = HVWord2Vec(config=Word2VecConfig(D=3000, n_epochs=3, min_word_freq=1))
        corpus = ("france paris england london germany berlin italy rome japan tokyo "
                  "france paris england london germany berlin italy rome japan tokyo "
                  "france england germany italy japan paris london berlin rome tokyo")
        w2v.train(corpus)
        return w2v

    def test_analogy_returns_result(self):
        w2v = self._build_w2v()
        analogizer = Analogizer(word2vec=w2v)
        result = analogizer.analogy("paris", "france", "tokyo")
        assert result is not None
        assert isinstance(result, AnalogyResult)
        assert result.c == "tokyo"

    def test_analogy_unknown_word(self):
        w2v = self._build_w2v()
        analogizer = Analogizer(word2vec=w2v)
        result = analogizer.analogy("paris", "france", "xyznonexistent")
        assert result is None

    def test_analogy_no_w2v(self):
        analogizer = Analogizer(word2vec=None)
        result = analogizer.analogy("a", "b", "c")
        assert result is None

    def test_find_relations(self):
        w2v = self._build_w2v()
        analogizer = Analogizer(word2vec=w2v)
        related = analogizer.find_relations("france", top_k=3)
        assert len(related) > 0
        # Countries should be related to countries
        assert related[0][0] in ["italy", "england", "germany", "japan"]

    def test_find_relations_no_w2v(self):
        analogizer = Analogizer(word2vec=None)
        result = analogizer.find_relations("test")
        assert result == []