ultk.language.semantics
Classes for modeling the meanings of a language.
Meanings are modeled as things which map linguistic forms to objects of reference. The linguistic forms and objects of reference can in principle be very detailed, and future work may elaborate the meaning classes and implement a Form class.
In efficient communication analyses, simplicity and informativeness can be measured as properties of semantic aspects of a language. E.g., a meaning is simple if it is easy to represent, or to compress into some code; a meaning is informative if it is easy for a listener to recover a speaker's intended literal meaning.
Examples:
>>> from ultk.language.semantics import Referent, Meaning, Universe >>> from ultk.language.language import Expression >>> # construct the meaning space for numerals >>> numerals_universe = NumeralUniverse(referents=[NumeralReferent(str(i)) for i in range(1, 100)]) >>> # construct a list of referents for the expression 'a few' >>> a_few_refs = [NumeralReferent(name=str(i)) for i in range(2, 6)] >>> a_few_meaning = NumeralMeaning(referents=a_few_refs, universe=numerals_universe) >>> # define the expression >>> a_few = NumeralExpression(form="a few", meaning=a_few_meaning)
1"""Classes for modeling the meanings of a language. 2 3 Meanings are modeled as things which map linguistic forms to objects of reference. The linguistic forms and objects of reference can in principle be very detailed, and future work may elaborate the meaning classes and implement a Form class. 4 5 In efficient communication analyses, simplicity and informativeness can be measured as properties of semantic aspects of a language. E.g., a meaning is simple if it is easy to represent, or to compress into some code; a meaning is informative if it is easy for a listener to recover a speaker's intended literal meaning. 6 7 Examples: 8 9 >>> from ultk.language.semantics import Referent, Meaning, Universe 10 >>> from ultk.language.language import Expression 11 >>> # construct the meaning space for numerals 12 >>> numerals_universe = NumeralUniverse(referents=[NumeralReferent(str(i)) for i in range(1, 100)]) 13 >>> # construct a list of referents for the expression 'a few' 14 >>> a_few_refs = [NumeralReferent(name=str(i)) for i in range(2, 6)] 15 >>> a_few_meaning = NumeralMeaning(referents=a_few_refs, universe=numerals_universe) 16 >>> # define the expression 17 >>> a_few = NumeralExpression(form="a few", meaning=a_few_meaning) 18""" 19 20from dataclasses import dataclass 21from functools import cached_property 22from typing import Any, Generic, TypeVar, Union 23from ultk.util.frozendict import FrozenDict 24 25import numpy as np 26import pandas as pd 27 28T = TypeVar("T") 29 30 31class Referent: 32 """A referent is some object in the universe for a language. 33 34 Conceptually, a Referent can be any kind of object. This functions like a generic python object that is _immutable_ after initialization. 35 At initialization, properties can be specified either by passing a dictionary or by keyword arguments. 36 """ 37 38 def __init__(self, name: str, properties: dict[str, Any] = {}, **kwargs) -> None: 39 """Initialize a referent. 40 41 Args: 42 name: a string representing the name of the referent 43 """ 44 self.name = name 45 self.__dict__.update(properties, **kwargs) 46 self._frozen = True 47 48 def __setattr__(self, __name: str, __value: Any) -> None: 49 if hasattr(self, "_frozen") and self._frozen: 50 raise AttributeError("Referents are immutable.") 51 else: 52 object.__setattr__(self, __name, __value) 53 54 def __str__(self) -> str: 55 return str(self.__dict__) 56 57 def __lt__(self, other): 58 return self.name < other.name 59 60 def __eq__(self, other) -> bool: 61 return self.name == other.name and self.__dict__ == other.__dict__ 62 63 def __hash__(self) -> int: 64 return hash((self.name, frozenset(self.__dict__.items()))) 65 66 def __repr__(self) -> str: 67 return f"Referent({self.name}, {self.__dict__})" 68 69 70@dataclass(frozen=True) 71class Universe: 72 """The universe is the collection of possible referent objects for a meaning.""" 73 74 referents: tuple[Referent, ...] 75 prior: tuple[float, ...] 76 77 def __init__(self, referents, prior=None): 78 # use of __setattr__ is to work around the issues with @dataclass(frozen=True) 79 object.__setattr__(self, "referents", referents) 80 # When only referents are passed in, make the priors a unifrom distribution 81 object.__setattr__( 82 self, "prior", prior or tuple(1 / len(referents) for _ in referents) 83 ) 84 85 @cached_property 86 def _referents_by_name(self): 87 return {referent.name: referent for referent in self.referents} 88 89 @cached_property 90 def size(self): 91 return len(self.referents) 92 93 @cached_property 94 def prior_numpy(self) -> np.ndarray: 95 return np.array(self.prior) 96 97 def __getitem__(self, key: Union[str, int]) -> Referent: 98 if type(key) is str: 99 return self._referents_by_name[key] 100 elif type(key) is int: 101 return self.referents[key] 102 else: 103 raise KeyError("Key must either be an int or str.") 104 105 def __str__(self): 106 referents_str = ",\n\t".join([str(point) for point in self.referents]) 107 return f"Points:\n\t{referents_str}\nDistribution:\n\t{self.prior}" 108 109 def __len__(self) -> int: 110 return len(self.referents) 111 112 @classmethod 113 def from_dataframe(cls, df: pd.DataFrame): 114 """Build a Universe from a DataFrame. 115 It's assumed that each row specifies one Referent, and each column will be a property 116 of that Referent. We assume that `name` is one of the columns of the DataFrame. 117 118 Args: 119 a DataFrame representing the meaning space of interest, assumed to have a column `name` 120 """ 121 records = df.to_dict("records") 122 referents = tuple(Referent(record["name"], record) for record in records) 123 default_prob = 1 / len(referents) 124 # prior = FrozenDict({ referent: getattr(referent, "probability", default_prob) for referent in referents }) 125 prior = tuple( 126 getattr(referent, "probability", default_prob) for referent in referents 127 ) 128 return cls(referents, prior) 129 130 @classmethod 131 def from_csv(cls, filename: str): 132 """Build a Universe from a CSV file. This is a small wrapper around 133 `Universe.from_dataframe`, so see that documentation for more information. 134 """ 135 df = pd.read_csv(filename) 136 return cls.from_dataframe(df) 137 138 139@dataclass(frozen=True) 140class Meaning(Generic[T]): 141 """A meaning maps Referents to any type of object. 142 143 For instance, sentence meanings are often modeled as sets of points (e.g. possible worlds). 144 These correspond to mappings from points (i.e. Referents) to truth values, corresponding to the characteristic function of a set. 145 But, in general, meanings can have a different output type for, e.g. sub-sentential meanings.. 146 147 Properties: 148 mapping: a `FrozenDict` with `Referent` keys, but arbitrary type `T` as values. 149 150 universe: a Universe object. The `Referent`s in the keys of `mapping` are expected to be exactly those in `universe`. 151 152 _dist: a mapping representing a probability distribution over referents to associate with the meaning. By default, will be assumed to be uniform over the "true-like" `Referent`s in `mapping` (see `.dist`). 153 """ 154 155 mapping: FrozenDict[Referent, T] 156 # With the mapping, `universe` is not conceptually needed, but it is very useful to have it lying around. 157 # `universe` should be the keys to `mapping`. 158 universe: Universe 159 # _dist: FrozenDict[Referent, float] = FrozenDict({}) 160 _dist = False # TODO: clean up 161 162 @property 163 def dist(self) -> FrozenDict[Referent, float]: 164 if self._dist: 165 # normalize weights to distribution 166 total_weight = sum(self._dist.values()) 167 return FrozenDict( 168 { 169 referent: weight / total_weight 170 for referent, weight in self._dist.items() 171 } 172 ) 173 else: 174 num_true_like = sum(1 for value in self.mapping.values() if value) 175 if num_true_like == 0: 176 raise ValueError("Meaning must have at least one true-like referent.") 177 return FrozenDict( 178 { 179 referent: (1 / num_true_like if self.mapping[referent] else 0) 180 for referent in self.mapping 181 } 182 ) 183 184 def is_uniformly_false(self) -> bool: 185 """Return True if all referents in the meaning are mapped to False (or coercible to False).In the case where the meaning type is boolean, this corresponds to the characteristic function of the empty set.""" 186 return all(not value for value in self.mapping.values()) 187 188 def __getitem__(self, key: Referent) -> T: 189 return self.mapping[key] 190 191 def __iter__(self): 192 """Iterate over the referents in the meaning.""" 193 return iter(self.mapping) 194 195 def __bool__(self): 196 return bool(self.mapping) # and bool(self.universe) 197 198 def __str__(self): 199 return "Mapping:\n\t{0}".format( 200 "\n".join(f"{ref}: {self.mapping[ref]}" for ref in self.mapping) 201 ) # \ \nDistribution:\n\t{self.dist}\n"
32class Referent: 33 """A referent is some object in the universe for a language. 34 35 Conceptually, a Referent can be any kind of object. This functions like a generic python object that is _immutable_ after initialization. 36 At initialization, properties can be specified either by passing a dictionary or by keyword arguments. 37 """ 38 39 def __init__(self, name: str, properties: dict[str, Any] = {}, **kwargs) -> None: 40 """Initialize a referent. 41 42 Args: 43 name: a string representing the name of the referent 44 """ 45 self.name = name 46 self.__dict__.update(properties, **kwargs) 47 self._frozen = True 48 49 def __setattr__(self, __name: str, __value: Any) -> None: 50 if hasattr(self, "_frozen") and self._frozen: 51 raise AttributeError("Referents are immutable.") 52 else: 53 object.__setattr__(self, __name, __value) 54 55 def __str__(self) -> str: 56 return str(self.__dict__) 57 58 def __lt__(self, other): 59 return self.name < other.name 60 61 def __eq__(self, other) -> bool: 62 return self.name == other.name and self.__dict__ == other.__dict__ 63 64 def __hash__(self) -> int: 65 return hash((self.name, frozenset(self.__dict__.items()))) 66 67 def __repr__(self) -> str: 68 return f"Referent({self.name}, {self.__dict__})"
A referent is some object in the universe for a language.
Conceptually, a Referent can be any kind of object. This functions like a generic python object that is _immutable_ after initialization. At initialization, properties can be specified either by passing a dictionary or by keyword arguments.
39 def __init__(self, name: str, properties: dict[str, Any] = {}, **kwargs) -> None: 40 """Initialize a referent. 41 42 Args: 43 name: a string representing the name of the referent 44 """ 45 self.name = name 46 self.__dict__.update(properties, **kwargs) 47 self._frozen = True
Initialize a referent.
Arguments:
- name: a string representing the name of the referent
71@dataclass(frozen=True) 72class Universe: 73 """The universe is the collection of possible referent objects for a meaning.""" 74 75 referents: tuple[Referent, ...] 76 prior: tuple[float, ...] 77 78 def __init__(self, referents, prior=None): 79 # use of __setattr__ is to work around the issues with @dataclass(frozen=True) 80 object.__setattr__(self, "referents", referents) 81 # When only referents are passed in, make the priors a unifrom distribution 82 object.__setattr__( 83 self, "prior", prior or tuple(1 / len(referents) for _ in referents) 84 ) 85 86 @cached_property 87 def _referents_by_name(self): 88 return {referent.name: referent for referent in self.referents} 89 90 @cached_property 91 def size(self): 92 return len(self.referents) 93 94 @cached_property 95 def prior_numpy(self) -> np.ndarray: 96 return np.array(self.prior) 97 98 def __getitem__(self, key: Union[str, int]) -> Referent: 99 if type(key) is str: 100 return self._referents_by_name[key] 101 elif type(key) is int: 102 return self.referents[key] 103 else: 104 raise KeyError("Key must either be an int or str.") 105 106 def __str__(self): 107 referents_str = ",\n\t".join([str(point) for point in self.referents]) 108 return f"Points:\n\t{referents_str}\nDistribution:\n\t{self.prior}" 109 110 def __len__(self) -> int: 111 return len(self.referents) 112 113 @classmethod 114 def from_dataframe(cls, df: pd.DataFrame): 115 """Build a Universe from a DataFrame. 116 It's assumed that each row specifies one Referent, and each column will be a property 117 of that Referent. We assume that `name` is one of the columns of the DataFrame. 118 119 Args: 120 a DataFrame representing the meaning space of interest, assumed to have a column `name` 121 """ 122 records = df.to_dict("records") 123 referents = tuple(Referent(record["name"], record) for record in records) 124 default_prob = 1 / len(referents) 125 # prior = FrozenDict({ referent: getattr(referent, "probability", default_prob) for referent in referents }) 126 prior = tuple( 127 getattr(referent, "probability", default_prob) for referent in referents 128 ) 129 return cls(referents, prior) 130 131 @classmethod 132 def from_csv(cls, filename: str): 133 """Build a Universe from a CSV file. This is a small wrapper around 134 `Universe.from_dataframe`, so see that documentation for more information. 135 """ 136 df = pd.read_csv(filename) 137 return cls.from_dataframe(df)
The universe is the collection of possible referent objects for a meaning.
78 def __init__(self, referents, prior=None): 79 # use of __setattr__ is to work around the issues with @dataclass(frozen=True) 80 object.__setattr__(self, "referents", referents) 81 # When only referents are passed in, make the priors a unifrom distribution 82 object.__setattr__( 83 self, "prior", prior or tuple(1 / len(referents) for _ in referents) 84 )
113 @classmethod 114 def from_dataframe(cls, df: pd.DataFrame): 115 """Build a Universe from a DataFrame. 116 It's assumed that each row specifies one Referent, and each column will be a property 117 of that Referent. We assume that `name` is one of the columns of the DataFrame. 118 119 Args: 120 a DataFrame representing the meaning space of interest, assumed to have a column `name` 121 """ 122 records = df.to_dict("records") 123 referents = tuple(Referent(record["name"], record) for record in records) 124 default_prob = 1 / len(referents) 125 # prior = FrozenDict({ referent: getattr(referent, "probability", default_prob) for referent in referents }) 126 prior = tuple( 127 getattr(referent, "probability", default_prob) for referent in referents 128 ) 129 return cls(referents, prior)
Build a Universe from a DataFrame.
It's assumed that each row specifies one Referent, and each column will be a property
of that Referent. We assume that name
is one of the columns of the DataFrame.
Arguments:
- a DataFrame representing the meaning space of interest, assumed to have a column
name
131 @classmethod 132 def from_csv(cls, filename: str): 133 """Build a Universe from a CSV file. This is a small wrapper around 134 `Universe.from_dataframe`, so see that documentation for more information. 135 """ 136 df = pd.read_csv(filename) 137 return cls.from_dataframe(df)
Build a Universe from a CSV file. This is a small wrapper around
Universe.from_dataframe
, so see that documentation for more information.
140@dataclass(frozen=True) 141class Meaning(Generic[T]): 142 """A meaning maps Referents to any type of object. 143 144 For instance, sentence meanings are often modeled as sets of points (e.g. possible worlds). 145 These correspond to mappings from points (i.e. Referents) to truth values, corresponding to the characteristic function of a set. 146 But, in general, meanings can have a different output type for, e.g. sub-sentential meanings.. 147 148 Properties: 149 mapping: a `FrozenDict` with `Referent` keys, but arbitrary type `T` as values. 150 151 universe: a Universe object. The `Referent`s in the keys of `mapping` are expected to be exactly those in `universe`. 152 153 _dist: a mapping representing a probability distribution over referents to associate with the meaning. By default, will be assumed to be uniform over the "true-like" `Referent`s in `mapping` (see `.dist`). 154 """ 155 156 mapping: FrozenDict[Referent, T] 157 # With the mapping, `universe` is not conceptually needed, but it is very useful to have it lying around. 158 # `universe` should be the keys to `mapping`. 159 universe: Universe 160 # _dist: FrozenDict[Referent, float] = FrozenDict({}) 161 _dist = False # TODO: clean up 162 163 @property 164 def dist(self) -> FrozenDict[Referent, float]: 165 if self._dist: 166 # normalize weights to distribution 167 total_weight = sum(self._dist.values()) 168 return FrozenDict( 169 { 170 referent: weight / total_weight 171 for referent, weight in self._dist.items() 172 } 173 ) 174 else: 175 num_true_like = sum(1 for value in self.mapping.values() if value) 176 if num_true_like == 0: 177 raise ValueError("Meaning must have at least one true-like referent.") 178 return FrozenDict( 179 { 180 referent: (1 / num_true_like if self.mapping[referent] else 0) 181 for referent in self.mapping 182 } 183 ) 184 185 def is_uniformly_false(self) -> bool: 186 """Return True if all referents in the meaning are mapped to False (or coercible to False).In the case where the meaning type is boolean, this corresponds to the characteristic function of the empty set.""" 187 return all(not value for value in self.mapping.values()) 188 189 def __getitem__(self, key: Referent) -> T: 190 return self.mapping[key] 191 192 def __iter__(self): 193 """Iterate over the referents in the meaning.""" 194 return iter(self.mapping) 195 196 def __bool__(self): 197 return bool(self.mapping) # and bool(self.universe) 198 199 def __str__(self): 200 return "Mapping:\n\t{0}".format( 201 "\n".join(f"{ref}: {self.mapping[ref]}" for ref in self.mapping) 202 ) # \ \nDistribution:\n\t{self.dist}\n"
A meaning maps Referents to any type of object.
For instance, sentence meanings are often modeled as sets of points (e.g. possible worlds). These correspond to mappings from points (i.e. Referents) to truth values, corresponding to the characteristic function of a set. But, in general, meanings can have a different output type for, e.g. sub-sentential meanings..
Properties:
mapping: a
FrozenDict
withReferent
keys, but arbitrary typeT
as values.universe: a Universe object. The
Referent
s in the keys ofmapping
are expected to be exactly those inuniverse
._dist: a mapping representing a probability distribution over referents to associate with the meaning. By default, will be assumed to be uniform over the "true-like"
Referent
s inmapping
(see.dist
).
163 @property 164 def dist(self) -> FrozenDict[Referent, float]: 165 if self._dist: 166 # normalize weights to distribution 167 total_weight = sum(self._dist.values()) 168 return FrozenDict( 169 { 170 referent: weight / total_weight 171 for referent, weight in self._dist.items() 172 } 173 ) 174 else: 175 num_true_like = sum(1 for value in self.mapping.values() if value) 176 if num_true_like == 0: 177 raise ValueError("Meaning must have at least one true-like referent.") 178 return FrozenDict( 179 { 180 referent: (1 / num_true_like if self.mapping[referent] else 0) 181 for referent in self.mapping 182 } 183 )
185 def is_uniformly_false(self) -> bool: 186 """Return True if all referents in the meaning are mapped to False (or coercible to False).In the case where the meaning type is boolean, this corresponds to the characteristic function of the empty set.""" 187 return all(not value for value in self.mapping.values())
Return True if all referents in the meaning are mapped to False (or coercible to False).In the case where the meaning type is boolean, this corresponds to the characteristic function of the empty set.