Projekt v PythonuZdroj ↗

Dědičnost (Heredity)

Napište AI pro posouzení pravděpodobnosti, že člověk bude mít určitý genetický znak.

$ python heredity.py data/family0.csv
Harry:
  Gene:
    2: 0.0092
    1: 0.4557
    0: 0.5351
  Trait:
    True: 0.2665
    False: 0.7335
James:
  Gene:
    2: 0.1976
    1: 0.5106
    0: 0.2918
  Trait:
    True: 1.0000
    False: 0.0000
Lily:
  Gene:
    2: 0.0036
    1: 0.0136
    0: 0.9827
  Trait:
    True: 0.0000
    False: 1.0000

Kdy úkol dokončit

Do 2027-06-30T23:59:00+00:00

Jak získat pomoc

  1. Ptejte se prostřednictvím Ed!
  2. Ptejte se prostřednictvím kterékoli z komunit CS50!

Výchozí informace

Mutované varianty genu GJB2 patří mezi hlavní příčiny poruch sluchu u novorozenců. Každý člověk nese dvě varianty genu, a může tedy mít 0, 1 nebo 2 kopie varianty GJB2 spojené s poruchou sluchu. Bez genetického testování však není snadné vědět, kolik kopií mutovaného GJB2 člověk má. Jde o „skrytý stav“: informaci s pozorovatelným účinkem, například poruchou sluchu, kterou však nemusíme přímo znát. Někteří lidé totiž mohou mít 1 nebo 2 kopie mutovaného GJB2, a přesto poruchu sluchu neprojevovat, zatímco jiní mohou mít poruchu sluchu i bez kopií mutovaného GJB2.

Každé dítě dědí jednu kopii genu GJB2 od každého rodiče. Pokud má rodič dvě kopie mutovaného genu, předá dítěti mutovaný gen. Pokud nemá žádnou kopii mutovaného genu, mutovaný gen dítěti nepředá. Pokud má jednu kopii, předá ji s pravděpodobností 0,5. Po předání genu však existuje určitá pravděpodobnost další mutace: varianty způsobující poruchu sluchu na variantu, která ji nezpůsobuje, nebo naopak.

Všechny tyto vztahy můžeme zkusit modelovat vytvořením bayesovské sítě všech relevantních proměnných, například jako níže pro rodinu se dvěma rodiči a jedním dítětem.

Bayesian Network for genetic traits

Každý člen rodiny má náhodnou proměnnou Gene představující počet kopií určitého genu, například varianty GJB2 spojené s poruchou sluchu: hodnotu 0, 1 nebo 2. Každý má také náhodnou proměnnou Trait, která je yes nebo no podle toho, zda se u něj znak založený na genu, například porucha sluchu, projevuje. Od Gene každého člověka vede šipka k jeho Trait, což vyjadřuje, že geny ovlivňují pravděpodobnost určitého znaku. Od proměnných Gene matky i otce navíc vedou šipky k Gene dítěte: geny dítěte závisí na genech rodičů.

Vaším úkolem je pomocí modelu odvozovat závěry o populaci. Na základě informací o lidech, jejich rodičích a tom, zda mají určitý pozorovatelný znak, například ztrátu sluchu, způsobovaný daným genem, AI odvodí pravděpodobnostní rozdělení genů každého člověka a také pravděpodobnostní rozdělení toho, zda se u jednotlivých lidí daný znak projeví.

Začínáme

Porozumění výchozímu kódu

Prohlédněte si jednu z ukázkových datových sad v adresáři data: otevřete data/family0.csv v textovém editoru nebo tabulkovém programu, například Google Sheets, Excelu či Apple Numbers. První řádek definuje sloupce CSV: name, mother, father a trait. Další řádek uvádí, že Harryho matkou je Lily a otcem James; prázdné pole trait znamená, že nevíme, zda Harry daný znak má. James nemá v datové sadě uvedené rodiče, což označují prázdná pole mother a father, a znak se u něj projevuje, jak uvádí 1 v poli trait. Lily také nemá v datové sadě uvedené rodiče, ale znak se u ní neprojevuje, jak uvádí 0 v poli trait.

Otevřete heredity.py a nejprve se podívejte na definici PROBS. PROBS je slovník obsahující konstanty pravděpodobností různých událostí. Všechny souvisejí s počtem kopií určitého genu, který dále označujeme jednoduše jako „gen“, a s tím, zda se u člověka na jeho základě projevuje určitý „znak“. Data jsou volně založena na pravděpodobnostech varianty genu GJB2 spojené s poruchou sluchu a samotné poruchy sluchu. Změnou hodnot byste ale mohli AI použít i pro odvozování závěrů o jiných genech a znacích!

Nejprve PROBS["gene"] představuje nepodmíněné pravděpodobnostní rozdělení genu, tedy pravděpodobnost, pokud o rodičích člověka nic nevíme. Podle dat ve výchozím kódu má člověk v populaci 1% pravděpodobnost dvou kopií genu, 3% pravděpodobnost jedné kopie a 96% pravděpodobnost žádné kopie.

Dále PROBS["trait"] představuje podmíněnou pravděpodobnost projevu znaku, například poruchy sluchu. Ve skutečnosti jde o tři rozdělení, jedno pro každou možnou hodnotu gene. PROBS["trait"][2] tedy udává rozdělení znaku při dvou kopiích genu: v tomto případě je pravděpodobnost projevu 65 % a neprojevení 35 %. Pokud má člověk 0 kopií genu, má 1% pravděpodobnost projevu a 99% pravděpodobnost neprojevení znaku.

Nakonec PROBS["mutation"] udává pravděpodobnost, že gen zmutuje z cílového genu na necílový nebo naopak. Pokud má například matka dvě kopie cílového genu, jednu dítěti předá, ale existuje 1% pravděpodobnost, že zmutuje na necílový gen. Naopak pokud matka žádnou kopii nemá a cílový gen nepředá, existuje 1% pravděpodobnost, že předaná kopie zmutuje na cílový gen. I když tedy ani jeden rodič nemá žádnou kopii cílového genu, jejich dítě může mít 1 nebo dokonce 2 kopie.

Vypočítané pravděpodobnosti budou nakonec založeny na těchto hodnotách v PROBS.

Nyní se podívejte na funkci main. Nejprve načte data ze souboru do slovníku people. people mapuje jméno každého člověka na další slovník s informacemi o něm: jménem, matkou a otcem, pokud jsou v datech uvedeni, a údajem o tom, zda u něj byl daný znak pozorován (True, pokud ano, False, pokud ne, a None, pokud to nevíme).

Poté main definuje slovník probabilities, kde jsou všechny pravděpodobnosti zpočátku 0. Právě to projekt nakonec vypočítá: pro každého člověka AI určí rozdělení počtu kopií genu i toho, zda má daný znak. Například probabilities["Harry"]["gene"][1] bude pravděpodobnost, že Harry má 1 kopii genu, a probabilities["Lily"]["trait"][False] pravděpodobnost, že se u Lily znak neprojevuje.

Pokud tento zápis neznáte, slovník probabilities se vytváří pomocí slovníkové komprehenze (dictionary comprehension) v Pythonu. Ta zde vytvoří jednu dvojici klíč–hodnota pro každého person ve slovníku people.

Tyto pravděpodobnosti chceme vypočítat na základě evidence: víme, že se u některých lidí znak projevuje, nebo neprojevuje, a chceme stanovit uvedené pravděpodobnosti. Z přednášky si připomeňte, že podmíněnou pravděpodobnost lze vypočítat sečtením všech sdružených pravděpodobností splňujících evidenci a následnou normalizací tak, aby každé rozdělení mělo součet 1. Vaším úkolem je implementovat tři funkce: joint_probability pro výpočet sdružené pravděpodobnosti, update pro její přidání do existujícího rozdělení a normalize, aby součet každého rozdělení nakonec byl 1.

Specifikace

Dokončete implementace joint_probability, update a normalize.

Funkce joint_probability má přijmout slovník lidí spolu s informacemi o tom, kdo má kolik kopií genu a u koho se projevuje znak. Má vrátit sdruženou pravděpodobnost všech těchto událostí.

  • Funkce přijímá čtyři vstupní hodnoty: people, one_gene, two_genes a have_trait.
    • people je slovník lidí popsaný v části Porozumění výchozímu kódu. Klíče představují jména a hodnoty jsou slovníky obsahující klíče mother a father. Můžete předpokládat, že buď jsou mother a father oba prázdné, tedy rodiče nejsou v datové sadě uvedeni, nebo mother a father oba odkazují na jiné lidi ve slovníku people.
    • one_gene je množina všech lidí, pro které chceme vypočítat pravděpodobnost jedné kopie genu.
    • two_genes je množina všech lidí, pro které chceme vypočítat pravděpodobnost dvou kopií genu.
    • have_trait je množina všech lidí, pro které chceme vypočítat pravděpodobnost přítomnosti znaku.
    • Pro člověka, který není v one_gene ani two_genes, chceme vypočítat pravděpodobnost žádných kopií genu. Pro člověka, který není v have_trait, chceme vypočítat pravděpodobnost, že znak nemá.
  • Pokud například rodinu tvoří Harry, James a Lily, volání této funkce s one_gene = {"Harry"}, two_genes = {"James"} a trait = {"Harry", "James"} má vypočítat pravděpodobnost, že Lily má nula kopií genu, Harry jednu kopii, James dvě kopie, u Harryho a Jamese se znak projevuje a u Lily nikoli.
  • U lidí bez uvedených rodičů použijte k určení pravděpodobnosti daného počtu kopií genu rozdělení PROBS["gene"].
  • U lidí s rodiči v datové sadě každý rodič dítěti náhodně předá jednu ze svých dvou kopií. Existuje pravděpodobnost PROBS["mutation"], že zmutuje z cílového genu na necílový nebo naopak.
  • Rozdělení PROBS["trait"] použijte k výpočtu pravděpodobnosti, že člověk daný znak má, nebo nemá.

Funkce update přidává novou sdruženou pravděpodobnost do existujících rozdělení v probabilities.

  • Funkce přijímá pět vstupních hodnot: probabilities, one_gene, two_genes, have_trait a p.
    • probabilities je slovník lidí popsaný v části Porozumění výchozímu kódu. Každý člověk je mapován na rozdělení "gene" a "trait".
    • one_gene je množina lidí s jednou kopií genu v současném sdruženém rozdělení.
    • two_genes je množina lidí se dvěma kopiemi genu v současném sdruženém rozdělení.
    • have_trait je množina lidí se znakem v současném sdruženém rozdělení.
    • p je pravděpodobnost sdruženého rozdělení.
  • Pro každého person v probabilities má funkce aktualizovat rozdělení probabilities[person]["gene"] a probabilities[person]["trait"] přidáním p k odpovídající hodnotě každého rozdělení. Všechny ostatní hodnoty mají zůstat nezměněné.
  • Pokud je například "Harry" současně v two_genes i have_trait, přičte se p do probabilities["Harry"]["gene"][2] a probabilities["Harry"]["trait"][True].
  • Funkce nemá vracet žádnou hodnotu: má pouze aktualizovat slovník probabilities.

Funkce normalize aktualizuje slovník pravděpodobností tak, aby každé rozdělení bylo normalizované, tedy mělo součet 1 při zachování vzájemných poměrů.

  • Funkce přijímá jedinou hodnotu: probabilities.
    • probabilities je slovník lidí popsaný v části Porozumění výchozímu kódu. Každý člověk je mapován na rozdělení "gene" a "trait".
  • Obě rozdělení každého člověka v probabilities má funkce normalizovat tak, aby součet jejich hodnot byl 1 a relativní poměry zůstaly stejné.
  • Pokud je například probabilities["Harry"]["trait"][True] rovno 0.1 a probabilities["Harry"]["trait"][False] rovno 0.3, funkce má první hodnotu změnit na 0.25 a druhou na 0.75: součet je nyní 1 a druhá hodnota je stále trojnásobkem první.
  • Funkce nemá vracet žádnou hodnotu: má pouze aktualizovat slovník probabilities.

V heredity.py neměňte nic kromě tří funkcí určených k implementaci ve specifikaci. Můžete však napsat další funkce a případně importovat další moduly standardní knihovny Pythonu. Pokud je znáte, můžete importovat také numpy nebo pandas, ale žádné další moduly Pythonu od třetích stran používat nemáte.

Příklad sdružené pravděpodobnosti

Abychom vám pomohli promyslet výpočet sdružených pravděpodobností, uvádíme níže příklad.

Uvažujte následující hodnotu people:

{
  'Harry': {'name': 'Harry', 'mother': 'Lily', 'father': 'James', 'trait': None},
  'James': {'name': 'James', 'mother': None, 'father': None, 'trait': True},
  'Lily': {'name': 'Lily', 'mother': None, 'father': None, 'trait': False}
}

Ukážeme výpočet joint_probability(people, {"Harry"}, {"James"}, {"James"}). Podle argumentů je one_gene rovno {"Harry"}, two_genes rovno {"James"} a have_trait rovno {"James"}. Jde tedy o pravděpodobnost, že Lily má 0 kopií genu a nemá znak, Harry má 1 kopii a nemá znak a James má 2 kopie a má znak.

Začneme Lily. Na pořadí lidí nezáleží, pokud násobíme správné hodnoty, protože násobení je komutativní. Lily má 0 kopií genu s pravděpodobností 0.96, tedy PROBS["gene"][0]. Pokud má 0 kopií, nemá znak s pravděpodobností 0.99, tedy PROBS["trait"][0][False]. Pravděpodobnost, že má 0 kopií genu a nemá znak, je tedy 0.96 * 0.99 = 0.9504.

Dále uvažujeme Jamese. James má 2 kopie genu s pravděpodobností 0.01, tedy PROBS["gene"][2]. Při dvou kopiích je pravděpodobnost projevu znaku 0.65. Pravděpodobnost, že má 2 kopie a má znak, je tedy 0.01 * 0.65 = 0.0065.

Nakonec uvažujeme Harryho. Jaká je pravděpodobnost, že má 1 kopii genu? To může nastat dvěma způsoby: buď gen získá od matky a nikoli od otce, nebo od otce a nikoli od matky. Jeho matka Lily má 0 kopií genu, takže Harry od ní získá gen s pravděpodobností 0.01, tedy PROBS["mutation"], protože jedinou možností je mutace; s pravděpodobností 0.99 od matky gen nezíská. Jeho otec James má 2 kopie genu, takže Harry od něj získá gen s pravděpodobností 0.99, tedy 1 - PROBS["mutation"], ale od své matky získá gen s pravděpodobností 0.01, pravděpodobností mutace. Oba případy lze sečíst: 0.99 * 0.99 + 0.01 * 0.01 = 0.9802, což je pravděpodobnost jedné kopie genu u Harryho. Poznámka překladatele: poslední věta o přenosu od matky věrně zachovává slovo „mother“ v originále. V kontextu popisu otce je tato formulace v původním textu matoucí.

Pokud má Harry 1 kopii genu, pravděpodobnost, že nemá znak, je 0.44, tedy PROBS["trait"][1][False]. Pravděpodobnost, že má 1 kopii genu a nemá znak, je tedy 0.9802 * 0.44 = 0.431288.

Celá sdružená pravděpodobnost je proto součinem uvedených hodnot všech tří lidí: 0.9504 * 0.0065 * 0.431288 = 0.0026643247488.

Nápověda

  • Připomeňte si, že sdruženou pravděpodobnost více událostí lze získat vynásobením jejich pravděpodobností. U každého dítěte ale pravděpodobnost určitého počtu genů závisí na genech rodičů.

Testování

Pokud chcete, můžete po nastavení check50 ve svém systému spustit následující příkaz pro ověření správnosti kódu. Není to povinné; můžete úkol jednoduše odevzdat podle kroků na konci specifikace a stejné testy se spustí na našem serveru. V každém případě kód také sami zkompilujte a otestujte!

check50 ai50/projects/2024/x/heredity

Následujícím příkazem zkontrolujte styl svého kódu pomocí style50.

style50 heredity.py

Jak úkol odevzdat

  1. Navštivte tento odkaz, přihlaste se účtem GitHub a klikněte na Authorize cs50. Poté zaškrtněte políčko udělující týmu kurzu přístup k vašim odevzdaným pracím a klikněte na Join course.
  2. Nainstalujte Git a volitelně nainstalujte submit50.
  3. Pokud máte nainstalovaný submit50, spusťte
    submit50 ai50/projects/2024/x/heredity
    

    Jinak pomocí Gitu odešlete práci do https://github.com/me50/USERNAME.git, kde USERNAME je vaše uživatelské jméno GitHub, do větve pojmenované ai50/projects/2024/x/heredity.

Práce by měla být ohodnocena do pěti minut. Pak můžete na https://cs50.me/cs50ai zobrazit aktuální postup!