W niniejszym opracowaniu rozważa się problem opracowania metody definiowania miary zastępowalności składników spożywczych w przepisach kulinarnych zgodnej z wymaganiami zdefiniowanymi przez użytkownika. Zakłada się, że zdefiniowanie jednej miary, która jednocześnie pokryłaby wszystkie możliwe wymagania jest niemożliwe, ponieważ składnik, który w jednym zastosowaniu jest substytutem niebudzącym zastrzeżeń (np. zastępienie schabu polędwicą wieprzową przy braku ograniczeń dietetycznych), w innym będzie nie do przyjęcia (np. zastępienie schabu polędwicą wieprzową w diecie wegańskiej).
W przedstawionej wersji przyjęto następujące założenia:
- zamianie poddawany jest jeden składnik na raz, a nie grupa składników;
- wynikiem zamiany jest jeden składnik, a nie grupa składników;
- nie rozważa się ilości produktów;
- nie uwzględnia się przepisu, na którego potrzeby dokonywana jest zamiana;
- miara powinna oferować użytkownikowi wyjaśnialność decyzji, w szczególności unikając złożonych modeli typu czarna skrzynka czy wykorzystywania wektorów zanurzeń;
- istnieją dwie, zasadniczo różniące się od siebie role: ekspert dziedzinowy, który w ramach przedstawionej metody definiuje niezbędne elementy, żeby uzyskać miarę zastępowalności pod konkretną specyfikację (np. zastępowanie w diecie cukrzycowej), a który ma całą niezbędną wiedzę, żeby ocenić wynik działania systemu oraz użytkownika końcowego, który korzysta z miary zdefiniowanej przez eksperta dziedzinowego i nie posiada niezbędnej wiedzy do oceny działania systemu.
W toku prac zidentyfikowano kilka wymiarów, które mogą wpływać na zamienianie składników. Należy zaznaczyć, że nie jest to lista wyczerpująca ani zakorzeniona w studiach literaturowych. Wymiary, poza wyjaśnieniem, opisane są też wprowadzaną twardością ograniczenia. Wyróżnia się ograniczenie twarde tzn. takie, którego naruszenie powoduje, że potencjalny subsytut jest nieprzydatny z punktu widzenia użytkownika, np. zastąpienie schabu polędwicą wieprzową w diecie wegańskiej; oraz ograniczenie miękkie tzn. takie, które może być lepiej lub gorzej spełnione, np. zastąpienie cukru białego miodem w diecie cukrzycowej. Wskazuje się też czy dana cecha jest wyłącznie cechą składnika (cecha własna), czy cechą składnika w kontekście przepisu (cecha kontekstowa).
- Funkcja/właściwość Niektóre składniki przepisów charakteryzują się specyficznymi cechami czy właściwościami, które muszą zostać zachowane podczas zamiany. Przykładowo proszek do pieczenia pełni zazwyczaj funkcję środka spulchniającego, która musi zostać zachowana podczas zamiany. Ograniczenie twarde; cecha kontekstowa.
- Kluczowe właściwości organoleptyczne Niektóre składniki wprowadzają konkretne właściwości organoleptyczne kluczowe z punktu widzenia niektórych przepisów. Przykładowo, w przepisie na stek z tuńczyka zastąpienie polędwicy z tuńczyka fasolą z puszki (bez dodatkowego procesu mającego na celu przerobienie fasoli) jest niedopuszczalne. Ograniczenie twarde; cecha kontekstowa.
- Drugorzędne właściwości organoleptyczne Zamiana powinna w miarę możliwości zachowywać smak, zapach całego przepisu itp. Ograniczenie miękkie; cecha kontekstowa.
- Dopuszczalność Proponowany składnik musi być akceptowalny dla konsumenta, np. zastąpienie schabu polędwicą wieprzową w diecie wegańskiej nie jest dopuszczalne. Ograniczenie twarde; cecha własna.
- Preferencja dietetyczna Wszyscy różnimy się preferencjami smakowymi, wyborami dietetycznymi itp. Przykładowo, fleksitarianie preferują niespożywanie mięsa, ale mięso nie jest dla nich składnikiem niedopuszczalnym. Ograniczenie miękkie; cecha własna.
- Brak współwystępowania Przypuszcza się, że produkty często występujące razem w przepisach nie są swoimi zastępnikami. Ograniczenie miękkie; cecha własna (ale wynikająca ze zbioru danych).
- Współdzielony kontekst Przypuszcza się, że produkty występujące często razem z takimi samymi zbiorami składników mogą być swoimi zastępnikami. Ograniczenie miękkie; cecha własna (ale wynikająca ze zbioru danych).
- Wartości odżywcze Sumaryczna wartość odżywcza przepisu powinna zostać zachowana. Ograniczenie miękkie; cecha własna (w ogólności cecha kontekstowa, ale w świetle przyjętych założeń o zamienianiu jednego składnika na raz staje się własna)
- Dostępność Składniki róznią się dostępnością sezonową i geograficzną. Ograniczenie miękkie; cecha własna (ale zależna od miejsca i czasu wykorzystania).
- Kategoria Przypuszcza się, że produkty z tej samej kategorii (np. mąki) są raczej lepszymi zastępnikami dla zadanego produktu niż produkty z zupełnie innej kategorii. Ograniczenie miękkie; cecha własna.
- Nazwa W przypadku składników nowoczesnych nazwa może wprost wskazywać czego jest to zastępnik (np. wegański boczek jako zastępnik wędzonego boczku). Niestety, należy zwrócić uwagę, że w przypadku składników tradycyjnych nazwa może być myląca (np. mąka ziemniaczana nie koniecznie dobrym zastępnikiem mąki pszennej). Ograniczenie miękkie; cecha własna.
Z punktu widzenia miary istotne są wyłącznie ograniczenia miękkie, ponieważ składniki niespełniające ograniczeń twardych w ogóle nie powinny być rozważane jako możliwe zastępniki. Ponadto, ze względu na przyjęte założenia, na aktualnym etapie mogą zostać uwzględnione wyłącznie cechy własne produktów. Pozostawia to następujące cechy: Drugorzędne właściwości organoleptyczne, Preferencja dietetyczna, Brak współwystępowania, Współdzielony kontekst, Wartości odżywcze, Dostępność, Kategoria, Nazwa.
W przedstawionym rozwiązaniu wykorzystano wyłącznie następujące cechy: Preferencja dietetyczna, Wartości odżywcze, Kategoria. Pozostałe cechy zostały w bieżącej wersji pominięte z następujących powodów:
- Drugorzędne właściwości organoleptyczne Planowano integrację z FlavorDB 12, [13], która póki co okazuje się niemożliwa ze względu na problemy z dostępnością usługi.
- Brak współwystępowania, Współdzielony kontekst Na obecnym etapie zbiór danych TASTEset [14] nie jest powiazany z ontologią FoodOn 15, [16].
- Dostępność Na wczesnym etapie projektu podjęto decyzję o całkowitym pominięciu tej cechy jako zbyt skomplikowanej do zamodelowania.
- Nazwa Ontologia FoodOn nie obfituje w nowoczesne produkty, które zyskałyby w ten sposób. Z drugiej strony porównywanie nazw wymaga zwykle dość skomplikowanych miar, co mogłoby stać w sprzeczności z założeniem o wyjaśnialności miary.
Przedstawiona metoda tworzenia miar nie jest zamknięta na wprowadzanie nowych cech i kolejne wersje mogą wykorzystywać wymienione powyżej cechy.
Jako bazę dla zaproponowanej metody tworzenia miar wykorzystano metodę wielokryterialnego wspomagania decyzji UTA [4]. Bardziej szczegółowe, a przystępnie napisane wprowadzenie do UTA można znaleźć w 5, poniżej natomiast przedstawiono podsumowanie najważniejszych aspektów. Ze względu na spodziewane grono odbiorców zapożycza się stosowane w uczeniu masznowym pojęcia, m.in., parametru jako wartości liczbowej dobieranej automatycznie w procesie optymalizacji oraz * hiperparametru* jako parametru konfiguracyjnego zwyczajowo ustawianego przez użytkownika. Należy podkreślić, że nie są to terminy zwyczajowo stosowane w kontekście metod wielokryterialnego wspomagania decyzji.
UTA zakłada, że istnieje pewien zbiór obiektów (wariantów) opisanych cechami liczbowymi. Każda z cech może być albo maksymalizowana albo minimalizowana (hiperparametr), znana jest też jej wartość najlepsza i najgorsza (hiperparamter). W UTA cechy transformowane są za pomocą niemalejących/nierosnących, nieujemnych funkcji odcinkami liniowych, tworząc tzw. cząstkowe funkcje użyteczności, przy czym liczba odcinków dla każdej z funkcji jest hiperparametrem, natomiast wartości współczynników kierunkowych i wyrazów wolnych są parametrami. Dla kryterium maksymalizowanego funkcja jest niemalejąca, natomiast dla kryterium minimalizowanego - nierosnąca.
Cząstkowe funkcje użyteczności są sumowane (po wszystkich cechach) do globalnej funkcji użyteczności U
, przy czym
wprowadza się dodatkowy czynnik regularyzacyjny wymagający, aby globalna funkcja użyteczności dla szutcznego obiektu
składającego się wyłącznie z najgorszych wartości cech wynosiła 0, natomiast dla sztucznego obiektu składającego się
wyłącznie z najlepszych wartości cech wynosiła 1.
Zakłada się ponadto, że pomiędzy niektórymi obiektami znana jest relacja preporządku, pełniąca rolę odpowiednika zbioru
uczącego: dla dwóch obiektów a
, b
wiadomo albo, że a
jest preferowany nad b
, co powinno zostać odwzorowane przez
globalną funkcję użyteczności jako U(a) > U(b)
, albo że są nierozróżnialne, co powinno zostać odwzorowane
jako U(a) = U(b)
.
W odróżnieniu od metod uczenia maszynowego zamiast optymalizacji gradientowej stosuje się reprezentację jako problem matematycznego programowania liniowego i rozwiązuje przy wykorzystaniu tzw. solwerów. Należy zaznaczyć, że nie jest to reprezentacja w formie całkowitoliczbowego programowania liniowego i w związku z tym znalezienie rozwiązania odbywa się w czasie wielomianowym.
Wynikiem działania UTA jest globalna funkcja użyteczności U
, która definiuje preporządek na wszystkich rozważanych
wariantach: a
jest preferowane nad b
jeżeli U(a) > U(b)
albo a
jest nierozróżnialne z b
jeżeli U(a) = U(b)
.
W kontekście rozważanego zagadnienia zastępowalności składników należy zwrócić uwagę, że za zdefiniowanie wartości
hiperparametrów odpowiedzialny jest ekspert dziedzinowy, a nie użytkownik końcowy. Użytkownik końcowy korzysta (za
pośrednictwem odpowiedniego interfejsu użytkownika) dopiero z gotowej globalnej funkcji użyteczności U
.
Ponadto należy zwrócić uwagę, że ranking jest niezbędnym elementem odkrywania preferencji eksperta dziedzinowego. Bez
rankingu można wyznaczyć tylko front Pareto, tzn. zbiór obiektów niezdominowanych przez żaden inny, natomiast nie można
zbudować rankingu, ponieważ bez wiedzy o względnej ważności cech, o żadnej parze wariantów na froncie Pareto nie da się
jednoznacznie powiedzieć, żeby jeden element był lepszy od drugiego. Dla przykładu rozważmy dwa obiekty: a
oraz b
oraz dwie maksymalizowane cechy. Niech a
będzie miał wartości na tych cechach (0.2, 0.8)
, natomiast b
odpowiednio (0.8, 0.2)
. Ponieważ cechy są maksymalizowane, więc b
jest lepsze od a
na pierwszej cesze,
natomiast a
jest lepsze od b
na drugiej cesze. Bez dodatkowej wiedzy o względnej ważności cech (w UTA
reprezentowanej przez parametry cząstkowych funkcji użyteczności) nie da się stwierdzić czy, z punktu widzenia eksperta
dziedznowego, a
jest lepsze od b
, b
jest lepsze od a
czy może są nierozróżnialne.
Konsultacja z prof. Miłoszem Kadzińskim, specjalistą w dziedzinie wielokryterialnego wspomagania decyzji pracującym w
Instytucie Informatyki Politechniki Poznańskiej, wykazała, że nie ma ogólnie przyjętej, powszechnie używanej biblioteki
metod wspomagania decyzji w Pythonie. W związku z prostotą UTA podjęto decyzję o samodzielnej implementacji przy
wykorzystaniu biblioteki cvxpy 6. Implementacja dostępna jest w klasie uta.RawUTA
w
pliku uta/rawuta.py. Konstruktor klasy RawUTA
przyjmuje dwa argumenty:
features
typuSequence[Tuple[int, float, float]]
- Sekwencja (np. lista) opisująca hiperparametry cech w formie trójek, składających się kolejno z: liczby odcinków liniowych, wartości najgorszej, wartości najlepszej.same_tier_is_equivalent
typubool
- Parametr konfiguracyjny wskazujący jak interpretować argumenty opisanej poniżej metodyadd
.
Obiekt klasy RawUTA
w ramach publicznego API oferuje trzy metody:
-
Dwuargumentową metodę
add
dodającą informacje o znanej relacji preporządku o następujących argumentach:reference_ranking
typuSequence[Collection[Any]]
, stanowiące sekwencję kolekcji identyfikatorów obiektów. Obiekty z kolekcjireference_ranking[i]
są preferowane nad obiektami z kolekcjireference_ranking[j]
dla wszystkichj>i
. Jeżelisame_tier_is_equivalent
było ustawione naTrue
, to obiekty w obrębie kolekcjireference_ranking[i]
(dla każdegoi
) są uznawane za nierozróżnialne; w przeciwnym razie nie są dodawane żadne ograniczenia dotyczące par obiektów z tej samej kolekcjireference_ranking[i]
.variants
typuMapping[Any, Sequence[float]]
stanowiące odwzorowanie między identyfikatorami obiektów używanymi wreference_ranking
, a wartościami cech w tym samym porządku, który był użyty w arugmenciefeatures
konstruktora.
Metodę
add
można wywoływać wielokrotnie w celu dodania kolejnych informacji o znanej relacji preporządku, przy czym należy zaznaczyć, żeadd
polega na globalnej unikalności identyfikatorów wreference_ranking
, tzn. w przypadku odwołania do obiektu z tym samym identyfikatorem w kolejnych wywołaniachadd
oczekuje się, że wartości wvariants
przypisane temu identyfikatorowi będą identyczne, a dodawane relacje spójne między sobą. -
Bezargumentową metodę
solve
, którą należy wywołać po wszystkich wywołaniachadd
w celu rozwiązania problemu programowania liniowego. -
Jednoargumenowej metody
U
, której jedyny argument to sekwencja wartości cech opisujących obiekt, dla którego ma być obliczona wartość globalnej funkcji użytecznościU
. Zwracany jest obiekt typucvxpy.Expression
, którego wartość liczbową można odczytać za pomocą polavalue
.
Testy jednostkowe oparte na 5, a zarazem przykłady użycia klasy uta.RawUTA
znajdują się w
pliku uta/test/test_rawuta.py.
Klasa uta.RawUTA
ma stosunkowo niewygodny interfejs. W związku z tym wprowadzono klasę uta.UTA
zdefiniowaną w
pliku uta/uta.py oraz wykorzystywane przez nią klasy uta.FeatureSet
oraz uta.FeatureDescriptor
zdefiniowane w pliku uta/FeaturerSet.py. uta.FeatureDescriptor
to dataclass
, której rolą jest
przechowywanie informacji o pojedynczej cesze: nazwie (pole name
typu str
), liczbie odcinków w funkcji odcinkami
liniowej (pole n
typu int
), wartości najgorszej (pole worst
typu float
) oraz najlepszej (pole best
typu float
).
uta.FeatureSet
to klasa abstrakcyjna, którą klasa uta.UTA
wykorzystuje do pozyskiwania wartości cech. Głównym
elementem klasy jest metoda compute
, która przyjmuje jako jedyny argument identyfikator obiektu, a zwraca listę cech
liczbowych typu List[float]
. Ta metoda domyślnie rzuca wyjątek NotImplemented
i musi zostać zaimplementowana w
klasach pochodnych. uta.FeatureSet
udostępnia tez jedno pole descriptors
typu List[FeatureDescriptor]
, które klasa
pochodna powinna wypełnić listą obiektów typu uta.FeatureDescriptor
o identycznej długości co lista wartości zwracana
przez compute
. W końcu udostępniona jest metoda compute_batch
która przyjmuje jako argument listę identyfikatorów, a
zwraca listę list cech List[List[float]]
, domyślnie wywołując compute
dla każdego identyfikatora i łącząc zwracane
listy cech w jedną listę dwuwymiarową. Implementacje mogą przeciążyć compute_batch
jeżeli mają możliwość bardziej
efektywnego jednoczesnego obliczania cech dla wielu obiektów na raz, np. przez zapytanie SPARQL wykorzystujące słowo
kluczowe VALUES
.
Klasa uta.UTA
ma API bardzo zbliżone do tego oferowanego przez uta.RawUTA
z następującymi różnicami:
- Argument
features
konstruktora jest typuList[FeatureSet]
, zostaje zapisany jako polefeatures
. - Metoda
add
nie ma argumentuvariants
, który nie jest potrzebny, ponieważ obliczanie wartości cech jest odpowiedzialnością obiektów z polafeatures
. - Metoda
U
przyjmuje identyfikator lub listę identyfikatorów obiektów, których cechy są obliczane za obiektów z polafeatures
. Zwracana jest wartość liczbowa funkcjiU
jeżeli przekazano jeden identyfikator lub lista wartości liczbowych funkcjiU
jeżeli przekazano listę identyfikatorów.
Zaobserwowano, że założenie o istnieniu jednego, globalnego rankingu składników może nie być adekwatnym modelem dla
miary zastępowalności składników. Wprowadzono w związku z tym pewną modyfikację API klasy uta.UTA
oraz dodatkową,
specjalizowaną implementację klasy uta.FeatureSet
nazwaną uta.RelativeFeatureSet
. Obie klasy są zaimplementowane w
pliku uta/RelativeUTA.py.
uta.RelativeFeatureSet
przyjmuje jako argument konstruktora obiekt klasy uta.FeatureSet
oraz poza standardowym
interfejsem uta.FeatureSet
udostępnia pole reference
, które domyślnie ma wartość None
, a które musi zostać
ustawione na identyfikator obiektu przed każdym wywołaniem metody compute
lub compute_batch
.
uta.RelativeFeatureSet
zamiast obliczać wartości cech w sposób bezwzględny (np. wartość energetyczna danego składnika
wyrażona w kcal), oblicza jako cechy wartość bezwzględną różnic między cechami obiektu przekazanego jako argument do
funkcji compute
oraz obiektu, którego identyfikator jest w polu reference
. Takie podejście umożliwia implementację
opisanej we wstępie koncepcji zachowywania wartości odżywczej. Zakłada się, że brak różnicy jest zawsze najlepszą
możliwą wartością.
uta.RelativeUTA
jest owinięciem (ang. wrapper) klasy uta.UTA
z następującymi różnicami:
- Argument konstruktora
features
jest przekazywany bezpośrednio do konstruktora klasyuta.UTA
, natomiastsame_tier_is_equivalent
jest zawsze ustawione naFalse
. - Metoda
add
przyjmuje trzy argumenty: identyfikator obiektu referencyjnegoreference
, listę identyfikatorów obiektów lepszych od referencyjnegobetter
oraz listę obiektów gorszychworse
.reference
jest ustawione jako wartość polareference
wszystkich obiektów klasyuta.RelativeFeatureSet
na liściefeatures
, a pozostałe dwa argumenty są łączone jako dwuwymiarowa lista[better, worse]
i przekazywane do metodyuta.UTA.add
. - Metoda
U
przyjmuje jako pierwszy argumentreference
identyfikator obiektu referencyjnego, który jest wykorzystywany tak samo jak w metodzieadd
. Drugi argument i wartość zwracana mają identyczną semantykę jak wuta.UTA.U
. - Wprowadzona jest pomocnicza metoda
recommend
, która przyjmuje dwa argumenty:reference
o semantyce j.w. orazvariants
stanowiący kolekcję identyfikatorów. Zwracana jest para typuTuple[Any, float]
, której pierwszy element to element kolekcjivariants
dla którego wartość funkcjiU
jest największa (przy ustalonymreference
), a drugi argument to wartość funkcjiU
. Ta funkcja pełni funkcję rekomendera, który dla zadanego obiektureference
ma wybrać najlepszą alternatywę z kolekcjivariants
.
Testy jednostkowe i zarazem przykłady użycia znajdują się w pliku uta/test/test_RelativeUTA.py. Testy integracyjne wraz z mniej abstrakcyjnymi przykładami użycia znajdują się w plikach test_diabetes.py, test_glutenfree.py oraz test_vegan.py.
Zakłada się, że każdy rozważany składnik jest identyfikowany za pomocą identyfikatorów (IRI) encji z grafu wiedzy. W
bieżącej wersji przyjęto, że centralną częścią grafu wiedzy jest ontologia FoodOn i składniki są identyfikowane za
pomocą IRI z jej przestrzeni nazw. Wczytywanie FoodOn zostało zaimplementowane w postaci metody foodon
w
pliku helpers.py, która nie przyjmuje argumentów, a zwraca obiekt klasy owlready2.Ontology
biblioteki
owlready2 3, [19] zawierający wczytaną ontologię. FoodOn oraz importowane przez nią ontologie są domyślnie pobierane z
Internetu, natomiast dla zwiększenia efektywności wykorzystywany jest wbudowany w bibliotekę owlready2 mechanizm
budowania pamięci podręcznej w katalogu cache/ontologies
.
Dodatkowo z FoodOn powiązano WikiFCD 17, [18], graf wiedzy integrujący tabele wartości odżwyczych pochodzące z różnych źródeł do wspólnej reprezentacji. Mimo początkowych nadziei, że WikiFCD jest mocno zintegrowane z FoodOn okazało się, że tak nie jest i jednocześnie
- wiele składników w WikiFCD nie jest oznaczonych identyfikatorami z FoodOn;
- wiele składników z FoodOn występuje w WikiFCD, ale nie ma przypisanych żadnych informacji o wartościach odżywczych.
W pliku wikifcd2foodon.json znajdują się wszystkie powiązania między encjami WikiFCD oraz FoodOn, wygenerowane 29.06.2022 za pomocą następującego zapytania SPARQL zadanego do końcówki https://wikifcd.wiki.opencura.com/query/:
PREFIX p: <http://wikifcd.wiki.opencura.com/prop/>
PREFIX ps: <http://wikifcd.wiki.opencura.com/prop/statement/>
SELECT * WHERE {
?item p:P309/ps:P309 ?foodon.
}
Łącznie jest 1145 powiązań, podczas gdy w FoodOn samych podklas klasy food product FOODON_00001002
jest 13989 (patrz
kod w załączniku I). Co więcej, jak wspomniano wcześniej, niektóre z istniejących powiązań są bezużyteczne do zbierania
informacji o wartościach odżywczych. Przykładowo, encja sorghum kernel FOODON_03309978
jest odwzorowana
w http://wikifcd.wiki.opencura.com/entity/Q569378. Niestety, WikiFCD
nie oferuje żadnych informacji o wartościach odżywczych dla tej encji.
Żeby rozwiązać oba problemy zaproponowano tymczasowe rozwiązanie polegające na ręcznym odwzorowywaniu identyfikatorów
FoodOn i WikiFCD w formie pliku tekstowego wikifcd2foodon.tsv. Podczas wczytywania pliku linie
puste, linie składające się wyłącznie z białych znaków oraz linie, w których pierwszy nie-biały znak to #
są
ignorowane. Pozostałe linie dzielone są po białych znakach i uwzględniane są wyłącznie pierwsze dwa elementy wynikające
z podziału. Oczekuje się, że pierwszy element będzie identyfikatorem z WikiFCD, albo w formie pełnego IRI, albo w formie
wyłącznie części lokalnej (ang. local part, 1). W tej drugiej sytuacji jest uzupełniany o
prefiks http://wikifcd.wiki.opencura.com/entity/
do utworzenia pełnego IRI. Analogicznie, dla drugiego elementu
oczekuje się, że jest to albo pełne IRI encji z FoodOn, albo część lokalna, która zostaje uzupełniona
prefiksem http://purl.obolibrary.org/obo/
. Odwzorowania w wikifcd2foodon.tsv mają priorytet nad
tymi zgromadzonymi w wikifcd2foodon.json, tzn. w razie odwzorowania encji z FoodOn w obu plikach
uwzględniane jest odwzorowanie z wikifcd2foodon.tsv.
Integracja z WikiFCD została zaimplementowana w formie klasy WikiFCD
w pliku WikiFCD.py. Wczytywanie
odwzorowań z plików odbywa się w bezparametrowym konstruktorze klasy, natomiast pobieranie informacji z WikiFCD odbywa
się za pomocą operatora []
, którego jedynym argumentem jest łańcuch znaków (str
) stanowiący pełne IRI encji z
FoodOn. Zwracana wartość to albo None
jeżeli nie znaleziono odwzorowania dla encji i w związku z tym nie można pobrać
danych z WikiFCD
, albo para typu Tuple[rdflib.URIRef, rdflib.Graph]
, gdzie pierwszy element pary to IRI z WikiFCD
odczytany z odwzorowań wczytanych w konstruktorze, a przedstawiony jako obiekt klasy URIRef
biblioteki rdflib 2, a
drugi element to graf RDF zawierający fragment WikiFCD opisujący encję, której IRI zostało zwrócone jako pierwszy
element pary.
Klasa WikiFCD
nie operuje na zrzucie WikiFCD, zamiast tego komunikuje się bezpośrednio z kopią WikiFCD dostępną w
Internecie. Dla zwiększenia efektywności klasa WikiFCD
tworzy pamięć podręczną zawierającą pobrane fragmenty grafów,
domyślnie znajdującą się w katalogu cache/wikifcd
, który jest tworzony w konstruktorze. Nie zaimplementowano
mechanizmu usuwania niepotrzebnych bądź nieaktualnych wpisów z pamięci podręcznej. Pamięć podręczna zorganizowana jest w
formie nieskompresowanych plików w formacie Turtle (z rozszerzeniem ttl
), o nazwach odpowiadających częściom lokalnym
identyfikatorom z WikiFCD.
W celu przejścia z reprezentacji w formie grafu wiedzy do reprezentacji w formie wektorów liczbowych, wymaganych przez
metodę UTA zaproponowano dwie klasy: OWLReadyClassMembershipFeatureSet
zaimplementowaną w
pliku OWLReadyClassMembershipFeatureSet.py oraz WikiFCDFeatureSet
zaimplementowaną w pliku WikiFCDFeatureSet.py. Obie klasy implementują klasę
abstrakcyjną uta.FeatureSet
.
Konstruktor klasy OWLReadyClassMembershipFeatureSet
oczekuje jako argumentów dwóch sekwencji zawierających obiekty
biblioteki owlready2 reprezentujące nazwane klasy lub wyrażenia klasowe, oznaczone łącznie dalej
jako ClassExpression
: positive_classes
oraz negative_classes
. Każde z wyrażeń z obu sekwencji tworzy odrębną
cechę, która przyjmuje wartości 0 lub 1. Dla wyrażeń z positive_classes
1 jest wartością najlepszą, podczas gdy
dla negative_classes
0 jest wartością najlepszą. Niech classes
reprezentuje sklejenie list positive_classes
i negative_classes
: classes = positive_classes + negative_classess
. Metoda compute
oczekuje jednego argumentu
typu ClassExpression
i zwraca wektor długości len(classes)
taki, że i
-ty element przyjmuje w nim wartość 1 jeżeli
argument jest podklasą i
-tego wyrażenia na liście classes
i 0 w przeciwnym przypadku (tzn. gdy nie można wykazać, że
jest podklasą).
Należy zwrócić uwagę, że owlready2 nie udostępnia efektywnego mechanizmu odpytywania o zachodzenie zawierania się
wyrażeń klasowych. Tymczasowo zastosowano przybliżone rozwiązanie za pomocą bardzo ograniczonej implementacji indukcji
strukturalnej zaimplementowanej w funkcji is_subclass
w pliku helpers.py. Przedstawiona implementacja
jest poprawna (ang. sound), ale nie kompletna (ang. complete) i docelowo powinna zostać zastąpiona rozwiązaniem, które
posiada obie te cechy.
Klasa WikiFCDFeatureSet
korzysta z opisanej wcześniej klasy WikiFCD
do zbierania danych o wartościach odżywczych z
WikiFDC. Graf zwrócony przez obiekt klasy WikiFDC
jest odpytywany za pomocą zapytań SPARQL o cztery, zdefiniowane
poniżej, wartości. W opisie wykorzystywana jest następująca konwencja: ?item
to obiekt, dla którego jest odczytywana
wartość; ?amount
to odczytywana wartość; prefiks p:
reprezentuje przestrzeń
nazw http://wikifcd.wiki.opencura.com/prop/
, psv:
- http://wikifcd.wiki.opencura.com/prop/statement/value/
, wikibase:
- http://wikiba.se/ontology#
, a wb:
- http://wikifcd.wiki.opencura.com/entity/
.
- Energia wyrażona w kilokaloriach za pomocą
wzorca
?item p:P6/psv:P6 [wikibase:quantityAmount ?amount; wikibase:quantityUnit wb:Q11 ]
- Białko w gramach za pomocą wzorca
?item p:P7/psv:P7 [wikibase:quantityAmount ?amount; wikibase:quantityUnit wb:Q8 ]
- Tłuszcz w gramach za pomocą wzorca
?item p:P8/psv:P8 [wikibase:quantityAmount ?amount; wikibase:quantityUnit wb:Q8 ]
- Sód w miligramach za pomocą
wzorca
?item p:P18/psv:P18 [wikibase:quantityAmount ?amount; wikibase:quantityUnit wb:Q15 ]
Reprezentacja wszystkich cech jest ustawiona jako składająca się z jednego odcinka liniowego. Białko jest ustawione jako
cecha maksymalizowana, natomiast pozostałe cechy jako minimalizowane. Metoda compute
oczekuje identyfikatora IRI z
onotologi FoodOn w formie łańcucha znaków str
albo obiektu, którego atrybut iri
będzie takim identyfikatorem, a
zwraca listę składającą się z czterech liczb zmiennoprzecinkowych odpowiadających opisanym powyżej cechom.
Przykłady użycia obu klas zawarte są w testach integracyjnych w plikach test_diabetes.py, test_glutenfree.py oraz test_vegan.py.
Na dzień 12.07.2022 ostatni udany dostęp do Internetowej kopii WikiFCD był 04.07.2022, od tego czasu zwracany jest kod błędu HTTP 503 Service Temporarily Unavailable. W związku z tym opracowano alternatywny sposób zbierania informacji o składnikach odżywczych korzystający z baz danych udostępnianych przez U.S. Department of Agriculture w serwisie Food Data Central (FDC): Foundation Foods oraz Global Branded Foods w wersjach z kwietnia 2022 20.
Wczytywanie danych z plików w formacie JSON zostało zaimplementowane w formie klasy FDC
w pliku FDC.py o
bezparametrowym konstruktorze oraz operatorze []
, który oczekuje jako jedynego argumentu łańcucha znaków (str
)
stanowiącego pełne IRI encji z FoodOn, bądź obiektu, którego atrybut iri
będzie takim łańcuchem znaków. Operator []
zwraca listę słowników, które zawierają dane z FDC. Klasa FDC
jest w stanie samodzielnie pobrać niezbędne dane z
serwisu FDC oraz, dla zwiększenia efektywności, zapisać ich kopie w pamięci podręcznej w katalogu cache/FDC
.
Odwzorowanie z IRI na wewnętrzne identyfikatory baz FDC odbywa się za pomocą dwóch plików tekstowych, w których każda
linia stanowi pojedyncze odwzorowanie i składa się kolejno z następujących pól rozdzielonych białymi znakami:
identyfikator FDC ID, IRI encji lub jego lokalna części oraz opcjonalny, ignorowany komentarz (do końca linii). Przy
wczytywaniu plików puste linie, linie składające się wyłącznie z białych znaków oraz linie, w których pierwszy nie-biały
znak to #
są ignorowane, a części lokalne są uzupełniane do pełnych IRI przez dodanie
prefiksu http://purl.obolibrary.org/obo/
. Obsługiwane jest odwzorowanie jednego IRI na wiele FDC ID, w takim
przypadku operator []
zwraca listę wszystkich odwozorowanych produktów. Odwzorowania zapisane w
pliku fdc2foodon.tsv są odwzorowaniami stworzonymi ręcznie, natomiast te w
pliku fdc2foodon-auto.tsv zostały stworzone automatycznie przy wykorzystaniu
funkcji generate_mappings
zaimplementowanej w pliku FDC.py. Automatyczne generowanie odwzorowań polegało na
połączeniu ze sobą encji FoodOn oraz produktów z bazy FDC dla których zbiór (bez powtórzeń) słów w etykiecie encji oraz
w polu description
produktu były identyczne z dokładnością do kolejności słów i wielkości liter. Wyrywkowa ręczna
analiza wskazała, że nie jest to idealna metoda, np. flour tortilla zostało odwzorowane w tortilla flour.
Ponieważ wczytywanie całych baz FDC
zajmuje dużo czas, zastosowano następujące optymalizacje:
- Wszystkie instancje klasy
FDC
współdzielą wczytane dane, a samo wczytywanie danych odbywa się przy pierwszym wywołaniu konstruktora. - Wszystkie produkty wymienione w plikach z odwzorowaniami są zapisywane w pamięci podręcznej, w
pliku
cache/FDC/mapped.json.gz
. Ten plik jest uznawany za nieaktualny i generowany na nowo, jeżeli którykolwiek z plików z odwzorowaniami (fdc2foodon.tsv , fdc2foodon-auto.tsv) ma świeższą datę modyfikacji niż plik z pamięci podręcznej.
Na bazie klasy FDC
zaimplementowano w pliku FDC.py klasę FDCFeatureSet
, która z punktu widzenia API oraz
zwracanych cech stanowi bezpośredni zastępnik dla klasy WikiFCDFeatureSet
. Ze względu na możliwośc odwzorowania jednej
encji w wiele produktów z FDC zastosowano uśrednianie, tzn. jako wartość danej cechy dla encji przyjmowana jest średnia
wartość danej cechy po wszystkich odzwzorowanych produktach. W związku z awarią WikiFDC zastąpiono w testach
integracyjnych odwołania do klasy WikiFCDFeatureSet
odwołaniami do klasy FDCFeatureSet
bez pogorszenia wyników
testów.
Założono, że w diecie cukrzycowej preferowane powinny być produkty o niskim indeksie glikemicznym (IG). Ani w FoodOn,
ani w WikiFCD indeks glikemiczny nie jest dostępny, stanowi on zatem złoty standard, z którego nie można bezpośrednio
skorzystać w obliczeniach, ale który można wykorzystać do oceny. W ramach testu przedstawionego w klasie Diabetes
zaimplementowanej w pliku test_diabetes.py wykorzystano
uta.RelativeUTA
oparte na cechach generowanych przez WikiFCDFeatureSet
oraz na ich wartościach zrelatywizowanych za
pomocą RelativeFeatureSet
. Jako złoty standard wykorzystano informacje pochodzące z 7. W ramach zbioru uczącego (
znanego preporządku) wykorzystano trzy mąki, przedstawione w kolejności od najlepszej do najgorszej: soybean
flour obo.FOODON_03302142
(niski IG), rye flour obo.FOODON_03302492
(średni IG), white rice
flour obo.FOODON_03307541
(wysoki IG). Jako zbiór kandydatów, z którego należało dokonać wyboru i jednocześnie zbiór
obiektów, które były zastępowane, wykorzystano następujące rodzaje mąki: tapioca flour obo.FOODON_03310681
(wysoki
IG),
brown rice flour obo.FOODON_00003351
(średni IG), potato starch obo.FOODON_03307543
(wysoki IG), chickpea
flour obo.FOODON_03304537
(niski IG). Obliczona miara we wszystkich przypadkach prawidłowo wskazała (przy użyciu
metody uta.RelativeUTA.recommend
) chickpea flour obo.FOODON_03304537
jako najlepszy zastępnik dla wszystkich
czterech rodzajów mąki, natomiast po usunięciu chickpea flour ze zbioru - brown rice flour obo.FOODON_00003351
jako zastępnik dla wszystkich trzech pozostałych rodzajów mąki.
Na podstawie 8 przyjęto, że w diecie bezglutenowej należy unikać pszenicy, jęczmienia, żyta oraz owsa. Opisany
przypadek użycia został zaimplementowany w klasie GlutenFree
w pliku test_glutenfree.py.
Wykorzystano uta.RelativeUTA
oraz trzy sposoby konstruowania cech:
- Przynależność do wyrażenia klasowego wheat food product OR barley food product OR oat food product OR rye food
product OR triticale food
product
FOODON_00001141 | FOODON_00001191 | FOODON_00001189 | FOODON_00001190 | FOODON_00002552
jako cecha minimalizowana obliczana za pomocąOWLReadyClassMembershipFeatureSet
- Przynależność do wyrażenia klasowego gravy or sauce
FOODON_00001931
jako cecha względna obliczana za pomocą klasRelativeFeatureSet
orazOWLReadyClassMembershipFeatureSet
- Różnice w wartościach odżywczych obliczane za pomocą
RelativeFeatureSet(WikiFCDFeatureSet())
Jako zbiór uczący przedstawiono dwa rankingi oparte o 9:
- Ranking I
- buckwheat noodle
FOODON_03309979
, shirataki noodleFOODON_03311767
- pasta
FOODON_03306347
- apple (whole, raw)
FOODON_03301710
- buckwheat noodle
- Ranking II
- sorghum seed (whole)
FOODON_00003751
, rice (polished)FOODON_03304560
- barley (pearled)
FOODON_03306062
- apple (whole, raw)
FOODON_03301710
- sorghum seed (whole)
Jako zbiór kandydatów przyjęto następujące encje: red kidney bean (canned) FOODON_03303520
, apple (whole,
raw) FOODON_03301710
, canola oil FOODON_03302578
, wheat gluten FOODON_03310809
,
lard FOODON_03302051
, white rice flour FOODON_03307541
, whole wheat flour FOODON_03302340
, tamari
sauce FOODON_03309556
. Testy potwierdzają, że obliczona dla podanej listy kandydatów prawidłowo wskazuje white rice
flour jako bezglutenowy odpowiednik whole wheat flour oraz tamari sauce jako bezglutenowy odpowiednik zarówno dla
tamari sauce jak i dla soy sauce FOODON_03301115
.
W nawiązaniu do dyskusji we wstępie należy podkreślić, że jeżeli mowa o diecie faktycznie bezglutenowej, a nie
niskoglutenowej, to zbiór potencjalnych zastępników musi być wstępnie przefiltrowany tak, żeby wybór najlepszego
zastępnika dokonywany był wyłącznie spośród produktów niezawierających glutenu. Należy również zaznaczyć, że FoodOn
wydaje się nie być dobrym źródłem wiedzy w tym zakresie, przykładowo sos sojowy wytwarzany jest przy udziale zarówno
soi, jak i pszenicy, co jednak nie wynika z opisu encji soy sauce FOODON_03301115
.
W klasie Vegan
zaimplementowanej w pliku test_vegan.py opracowano przypadek wyboru subsytutu w diecie
wegańskiej. Wykorzystano uta.RelativeUTA
oraz dwa zbiory cech:
- Przynależność do klas obliczana za pomocą
OWLReadyClassMembershipFeatureSet
, gdzie zbiór klas pozytywnych składał się z jednego wyrażenia klasowego algal food product OR fungus food product OR microbial food product OR plant food product OR plant based refined or partially-refined food product OR plant based meat product analogFOODON_00001184 | FOODON_00001143 | FOODON_00001145 | FOODON_00001015 | FOODON_00002131 | FOODON_00002129
natomiast zbiór klas negatywnych z dwóch wyrażeń: vertebrate animal food product OR seafood product OR invertebrate food productFOODON_00001092 | FOODON_00001046 | FOODON_00001176
oraz spice or herbFOODON_00001242
- Różnice w wartościach odżywczych obliczane za pomocą
RelativeFeatureSet(WikiFCDFeatureSet())
Jako zbiór uczący wykorzystano dwa rankingi:
- Opracowany na podstawie 10:
- imitation bacon bit
FOODON_03305199
- bacon (smoked)
FOODON_03309992
- apple (whole, raw)
FOODON_03301710
- imitation bacon bit
- Opracowany na podstawie 11:
- tempeh
FOODON_03304999
, tofu food productFOODON_03309664
, vegetable protein ( textured)FOODON_03311469
- beef (raw)
FOODON_03309737
, turkey (raw, ground)FOODON_03311109
- thyme (dried)
FOODON_03301215
, vanilla bean (whole)FOODON_00003738
- tempeh
Rozważano zbiór składający się z pięciu możliwych zastępników: red kidney bean (canned) FOODON_03303520
, apple (
whole, raw) FOODON_03301710
, canola oil FOODON_03302578
, wheat gluten FOODON_03310809
,
lard FOODON_03302051
. W ramach testów wykazano, że zarówno beef (raw) FOODON_03309737
jak i pork
(fresh) FOODON_03317271
są prawidłowo zamieniane na red kidney bean (canned); tallow (
edible) FOODON_03315521
na canola oil (zamiast na lard), a honey UBERON_0036016
na apple (whole, raw).
Jeżeli poniższa bibliografia wydaje się niekompletna to znaczy, że pozycje, które składają się wyłącznie z odnośników do stron Internetowych zostały zintegrowane jako odnośniki bezpośrednio w tekście. Pełna treść bibliografii znajduje się w pliku README.md.
[4]: Jacquet-Lagréze, E. and J. Siskos, “Assessing a Set of Additive Utility Functions for Multicriteria Decision Making: The UTA Method,” Eur J of Oper Res, 10(2), 1982, 151-164.
[13]: Garg N, Sethupathy A, Tuwani R, Nk R, Dokania S, Iyer A, Gupta A, Agrawal S, Singh N, Shukla S, Kathuria K, Badhwar R, Kanji R, Jain A, Kaur A, Nagpal R, Bagler G. FlavorDB: a database of flavor molecules. Nucleic Acids Res. 2018 Jan 4;46(D1):D1210-D1216. doi: 10.1093/nar/gkx957. PMID: 29059383; PMCID: PMC5753196.
[14]: Anna Wróblewska, Agnieszka Kaliska, Maciej Pawlowski, Dawid Wisniewski, Witold Sosnowski, Agnieszka Lawrynowicz: TASTEset - Recipe Dataset and Food Entities Recognition Benchmark. CoRR abs/2204.07775 (2022) https://arxiv.org/abs/2204.07775
[16]: Dooley, D.M., Griffiths, E.J., Gosal, G.S. et al. FoodOn: a harmonized food ontology to increase global food traceability, quality control and data integration. npj Sci Food 2, 23 (2018). https://doi.org/10.1038/s41538-018-0032-6
[18]: Katherine Thornton, Kenneth Seals-Nutt, Mika Matsuzaki: Introducing WikiFCD: Many Food Composition Tables in a Single Knowledge Base. JOWO 2021
[19]: Lamy JB. Owlready: Ontology-oriented programming in Python with automatic classification and high level constructs for biomedical ontologies. Artificial Intelligence In Medicine 2017;80:11-28
from helpers import foodon
queue = [foodon().search_one(iri="http://purl.obolibrary.org/obo/FOODON_00001002")]
visited = set()
while len(queue) > 0:
e = queue.pop(0)
if e not in visited:
visited.add(e)
queue += e.descendants()
print("Visited", len(visited))