cs50ai2

cs50ai2-------Uncertainty


基础知识

在这节课中,前面主要介绍了一些概率论的基础知识,比如说条件概率、贝叶斯规则、联合概率以及一些概率公式等等
贝叶斯规则:
img
概率公式:
img

接着介绍了贝叶斯网络
它是用来表示随机变量之间的依赖关系的一种数据结构
img
比如说像上面这样一个简化的贝叶斯网络,具有基本的四个特征:
(1)有向图结构
(2)图中的每个结点代表着一个随机变量
(3)x指向y的箭头代表着x是y的父节点,即y的概率分布依赖于x的值
(4)每个节点都存储着P(X | Parents(X))的概率

利用贝叶斯网络,我们可以计算某些联合概率:
img

也可以利用枚举推理的思想,去计算条件概率:
img
img

但是随着变量个数的增加,显然枚举推理会变得极其低效,因此又引入了近似推理的思想。

这里提了一种拒绝采样的方法,针对有观测变量的情况,即我们已经有了某些evidence,比如说此时下雨或者火车按时到站,我们进行采样,如果得到样本的值与我们观测到的情况相同,那么我们就接受这个样本,否则就要重新采样
这样做就会导致我们采样的效率很低,所以又引入了似然加权采样,在进行采样时,不再对观测变量采样,只对非观测变量采样,观测变量就直接赋我们的观测值,将这样得到的样本赋一个重要性权值

接下来又简要介绍了一下马尔科夫模型
马尔科夫假设是指,当前的状态只取决于有限固定数量的先前状态
而马尔科夫链指的是,一组随机变量的次序,其中每个变量的分布都遵循马尔科夫假设,也就是说链中每个事件发生的概率都取决于先前的事件,而要建立一个马尔科夫链,我们需要一个转移模型,根据当前事件的的可能值指定下一个事件的概率分布
img
img
隐藏马尔可夫模型是马尔可夫模型的一种,适用于具有隐藏状态的系统,该系统会生成一些观察到的事件,这意味着有时人工智能可以对世界进行一些观测,但无法了解世界的精确状态。 在这些情况下,世界的状态称为隐藏状态,人工智能可以访问的任何数据都是观察结果,我们可以建立一个感知模型来代表观测变量与状态的关系,再结合之前的转移模型,就可以建立隐藏马尔科夫链
img

img

课后题目

(1) pagerank
background:
这个题目主要是关于网页重要性(代表着该网页被访问的概率)的计算,我们可以采用迭代的方法来计算,令 PR(p) 为给定页面 p 的 PageRank:随机冲浪者最终访问该页面的概率,我们知道随机冲浪者可以通过两种方式进入该页面:

冲浪者以 1 - d 的概率随机选择一个页面并最终到达页面 p。
冲浪者以概率 d 跟随从页面 i 到页面 p 的链接。

第一个条件用数学方式表达起来相当简单:它是 1 - d 除以 N,其中 N 是总页数。 这是因为随机选择页面的一维概率在所有 N 个可能的页面之间平均分配。

对于第二个条件,我们需要考虑链接到页面 p 的每个可能的页面 i。 对于每个传入页面,令 NumLinks(i) 为页面 i 上的链接数。 链接到 p 的每个页面 i 都有自己的 PageRank,PR(i),表示我们在任何给定时间位于页面 i 上的概率。 由于从页面 i 开始,我们以相同的概率访问该页面的任何链接,因此我们将 PR(i) 除以链接数量 NumLinks(i),以获得我们位于页面 i 并选择指向页面 p 的链接的概率。

这为我们提供了页面 p 的 PageRank 的以下定义

img

在此公式中,d 是阻尼因子,N 是页面总数,i 范围涵盖链接到页面 p 的所有页面,NumLinks(i) 是页面 i 上存在的链接数量。

那么我们如何计算每个页面的 PageRank 值呢? 我们可以通过迭代来做到这一点:首先假设每个页面的 PageRank 是 1 / N(即,在任何页面上的可能性相同)。 然后,根据之前的 PageRank 值,使用上述公式计算每个页面的新 PageRank 值。 如果我们不断重复这个过程,根据前一组 PageRank 值计算每个页面的一组新的 PageRank 值,最终 PageRank 值将收敛(即,每次迭代的变化不会超过一个小阈值)。

在此项目中,实现这两种计算 PageRank 的方法 - 通过从马尔可夫链随机冲浪者中采样页面并迭代应用 PageRank 公式来计算。

understanding:
打开 pagerank.py。 首先请注意文件顶部两个常量的定义: DAMPING 表示阻尼系数,最初设置为 0.85。 SAMPLES 表示我们将使用采样方法来估计 PageRank 的样本数量,最初设置为 10,000 个样本。

现在,看一下 main 函数。 它需要一个命令行参数,该参数是我们要计算 PageRank 的网页语料库的目录名称。 抓取函数获取该目录,解析该目录中的所有 HTML 文件,并返回表示语料库的字典。 该字典中的键代表页面(例如“2.html”),字典的值是由该键链接到的所有页面的集合(例如{“1.html”,“3.html”) })。

main函数然后调用sample_pagerank函数,其目的是通过采样来估计每个页面的PageRank。 该函数将crawl函数生成的页面语料库以及阻尼因子和要使用的样本数量作为参数。 最终,sample_pagerank 应返回一个字典,其中键是每个页面名称,值是每个页面的估计 PageRank(0 到 1 之间的数字)。

main函数还调用iterate_pagerank函数,该函数也会计算每个页面的PageRank,但使用迭代公式方法而不是采样。 返回值应采用相同的格式,并且我们希望在给定相同的语料库时这两个函数的输出应该相似

(2) Heredity
background:
每个孩子都会从父母那里继承一份 GJB2 基因。 如果父母有两个突变基因拷贝,那么他们会将突变基因遗传给孩子; 如果父母没有突变基因的拷贝,那么他们不会将突变基因遗传给孩子; 如果父母一方有一份突变基因,那么该基因遗传给孩子的概率为 0.5。 然而,基因传递后,它有可能会发生额外的突变:从导致听力障碍的基因版本变为不会造成听力障碍的基因版本,反之亦然。
我们可以尝试通过形成所有相关变量的贝叶斯网络来对所有这些关系进行建模,如下所示,它考虑了一个由两个父母和一个孩子组成的家庭。
img
您在此项目中的任务是使用此模型对总体进行推断。 给定有关人的信息,他们的父母是谁,以及他们是否具有由给定基因引起的特定可观察特征(例如听力损失),您的人工智能将推断出每个人基因的概率分布,以及人是否会表现出相关的特征的概率分布。

understanding:
PROBS 是一个字典,包含许多表示各种不同事件的概率的常量。 所有这些事件都与一个人拥有多少个特定基因(以下简称“gene”)副本以及一个人是否表现出特定特征(以下简称“trait”)有关. 这里的数据大致基于 GJB2 基因的听力障碍版本和听力障碍特征的概率.

首先,PROBS[“gene”] 表示该基因的无条件概率分布(即如果我们对该人的父母一无所知

接下来,PROBS[“trait”] 表示一个人表现出某种特征(如听力障碍)的条件概率

最后,PROBS[“mutation”] 是基因从相关基因突变为非该基因的概率,反之亦然

最终,您计算的概率将基于 PROBS 中的这些值。

现在,看一下 main 函数。该函数首先将文件中的数据加载到 people 字典中。人们将每个人的名字映射到另一个包含有关他们的信息的字典:包括他们的名字、他们的母亲(如果在数据集中列出了)、他们的父亲(如果在数据集中列出了),以及他们是否被观察到有问题的特征(如果有则为 True,如果没有则为 False,如果我们不知道则为 None)。

接下来,main 定义一个概率字典,所有概率最初设置为 0。这就是您的项目最终将计算的内容:对于每个人,您的 AI 还将计算他们拥有多少基因副本的概率分布 就看他们是否有这个特质。 例如,概率[“Harry”][“gene”][1] 是哈利拥有 1 个基因副本的概率,概率[“Lily”][“trait”][False] 是莉莉没有表现出这种特征的概率。

最终,我们希望根据一些证据来计算这些概率:鉴于我们知道某些人具有或不表现出该特征,我们希望确定这些概率。 回想一下课程中的内容,我们可以通过将满足证据的所有联合概率相加来计算条件概率,然后对这些概率进行归一化,使它们的总和为 1。您在这个项目中的任务是实现三个函数来实现这一点 :joint_probability 计算联合概率,update以将新计算的联合概率添加到现有概率分布中,然后进行normalize以确保所有概率分布最终总和为 1。

specification:
在计算joint probability时,对于任何不在 one_gene 或two_genes 中的人,我们想计算他们没有该基因副本的概率; 对于任何不属于have_trait的人,我们想计算他们不具有该特质的概率。
例如,如果家庭由 Harry、James 和 Lily 组成,则调用此函数,其中 one_gene = {"Harry"}、two_genes = {"James"} 和 Trait = {"Harry", "James"} 应计算 莉莉有零个基因拷贝,哈利有一个基因拷贝,詹姆斯有两个基因拷贝,哈利表现出该特征,詹姆斯表现出该特征,而莉莉没有表现出该特征的概率。

在update函数中,对于概率中的每个人 person,该函数应通过将 p 添加到每个分布中的适当值来更新概率[person]["gene"] 分布和概率[person]["trait"] 分布。 所有其他值应保持不变。
例如,如果“Harry”同时出现在two_genes和have_trait中,则p将被添加到概率[“Harry”][“gene”][2]和概率[“Harry”][“trait”][True ]。

在normalize函数中,对于每个人的两种概率分布,此函数应对该分布进行归一化,以便分布中的值总和为 1,并且分布中的相对值相同。
例如,如果概率["Harry"]["trait"][True]等于0.1并且概率["Harry"]["trait"][False]等于0.3,那么你的函数应该更新前一个值 变为 0.25,后一个值为 0.75:现在数字之和为 1,并且后一个值仍然是前一个值的三倍。

代码实践

(1)PageRank:
首先是transition model:
着一部分主要根据前面提到的pagerank,根据当前页面,计算得到其它页面可能被访问的概率分布

def transition_model(corpus, page, damping_factor):
    """
    Return a probability distribution over which page to visit next,
    given a current page.

    With probability `damping_factor`, choose a link at random
    linked to by `page`. With probability `1 - damping_factor`, choose
    a link at random chosen from all pages in the corpus.
    """
    prop_dist = {}

    dict_len = len(corpus.keys())
    pages_len = len(corpus[page])

    if len(corpus[page]) == 0:
        for key in corpus.keys():
            prop_dist[key] = 1 / dict_len

    else:
        random_factor = (1 - damping_factor) / dict_len
        even_factor = damping_factor / pages_len
        for key in corpus.keys():
            if key not in corpus[page]:
                prop_dist[key] = random_factor
            else:
                prop_dist[key] = even_factor + random_factor

    return prop_dist

然后根据前面的transition model,使用马尔科夫采样来估计每个页面的pagerank
每次采样都利用random函数根据转移模型得到的概率,来选择页面,同时对应的页面计数加1,在采样结束之后,在除以采样数来得到各个页面的pagerank

def sample_pagerank(corpus, damping_factor, n):
    """
    Return PageRank values for each page by sampling `n` pages
    according to transition model, starting with a page at random.

    Return a dictionary where keys are page names, and values are
    their estimated PageRank value (a value between 0 and 1). All
    PageRank values should sum to 1.
    """
    samples_dict = corpus.copy()
    for i in samples_dict:
        samples_dict[i] = 0
    sample = random.choice(list(corpus.keys()))
    samples_dict[sample] += 1
    for _ in range(n):
        dist = transition_model(corpus, sample, damping_factor)
        dist_lst = list(dist.keys())
        dist_weights = [dist[i] for i in dist]
        sample = random.choices(dist_lst, dist_weights, k=1)[0]
        samples_dict[sample] += 1

    for item in samples_dict:
        samples_dict[item] /= n

    return samples_dict

这里是第二种计算pagerank的方法,通过之前的pagerank计算式进行迭代,当所有页面的pagerank变化都小于0.001时,认为pagerank收敛:

def iterate_pagerank(corpus, damping_factor):
    """
    Return PageRank values for each page by iteratively updating
    PageRank values until convergence.

    Return a dictionary where keys are page names, and values are
    their estimated PageRank value (a value between 0 and 1). All
    PageRank values should sum to 1.
    """
    num_pages = len(corpus)
    initial_rank = 1.0 / num_pages
    current_rank = {page: initial_rank for page in corpus}
    new_rank = {page: 0 for page in corpus}

    # 定义一个函数来计算给定页面的PageRank值
    def calculate_pagerank(page):
        rank = (1 - damping_factor) / num_pages
        rank += damping_factor * sum(current_rank[link] / len(corpus[link]) for link in corpus if page in corpus[link])
        return rank

    # 开始迭代计算PageRank值,直到收敛
    while True:
        # 遍历每个页面,计算新的PageRank值
        for page in corpus:
            new_rank[page] = calculate_pagerank(page)

        # 检查是否收敛,如果所有页面的PageRank值变化都小于0.001,则退出循环
        convergence = all(abs(new_rank[page] - current_rank[page]) < 0.001 for page in corpus)
        if convergence:
            break

        # 将新的PageRank值复制到current_rank以进行下一轮迭代
        current_rank = new_rank.copy()

    # 归一化PageRank值,使其总和为1
    total_rank = sum(new_rank.values())
    normalized_rank = {page: rank / total_rank for page, rank in new_rank.items()}

    return normalized_rank

(2)Heredity:
计算联合概率:
这里我们根据题目提示计算即可
先分为父母已知与父母未知的情况,父母未知的情况比较简单,直接将gene probability与trait probability相乘即可,
而父母已知时,基因概率就要分开考虑,要考虑到具体的遗传情况,以及遗传过程中的变异情况

def joint_probability(people, one_gene, two_genes, have_trait):
    """
    Compute and return a joint probability.

    The probability returned should be the probability that
        * everyone in set `one_gene` has one copy of the gene, and
        * everyone in set `two_genes` has two copies of the gene, and
        * everyone not in `one_gene` or `two_gene` does not have the gene, and
        * everyone in set `have_trait` has the trait, and
        * everyone not in set` have_trait` does not have the trait.
    """
    probability = 1

    for person in people:
        gene_number = 1 if person in one_gene else 2 if person in two_genes else 0
        trait = True if person in have_trait else False

        gene_numb_prop = PROBS['gene'][gene_number]
        trait_prop = PROBS['trait'][gene_number][trait]

        if people[person]['mother'] is None:
            probability *= gene_numb_prop * trait_prop
        else:
            mother = people[person]['mother']
            father = people[person]['father']
            percentages = {}
            # 计算得到遗传给后代目标基因的概率
            for ppl in [mother, father]:
                number = 1 if ppl in one_gene else 2 if ppl in two_genes else 0
                perc = 0 + PROBS['mutation'] if number == 0 else 0.5 if number == 1 else 1 - PROBS['mutation']
                percentages[ppl] = perc
           # 分为父母一个基因都不遗传,有一个人遗传了目标基因,两个人都遗传了目标基因
            if gene_number == 0:
                probability *= (1 - percentages[mother]) * (1 - percentages[father])
            elif gene_number == 1:
                probability *= (1 - percentages[mother]) * percentages[father] + percentages[mother] * (
                            1 - percentages[father])
            else:
                probability *= percentages[mother] * percentages[father]

            probability *= trait_prop

    return probability

更新概率以及归一化函数:

def update(probabilities, one_gene, two_genes, have_trait, p):
    """
    Add to `probabilities` a new joint probability `p`.
    Each person should have their "gene" and "trait" distributions updated.
    Which value for each distribution is updated depends on whether
    the person is in `have_gene` and `have_trait`, respectively.
    """
    for person in probabilities:
        gene_number = 1 if person in one_gene else 2 if person in two_genes else 0
        probabilities[person]["gene"][gene_number] += p
        probabilities[person]["trait"][person in have_trait] += p


def normalize(probabilities):
    """
    Update `probabilities` such that each probability distribution
    is normalized (i.e., sums to 1, with relative proportions the same).
    """
    for person in probabilities:
        # 获取基因分布
        gene_distribution = probabilities[person]["gene"]
        # 获取特征分布
        trait_distribution = probabilities[person]["trait"]
        # 计算基因分布的总和
        gene_sum = sum(gene_distribution.values())
        # 计算特征分布的总和
        trait_sum = sum(trait_distribution.values())
        # 归一化基因分布
        for gene in gene_distribution:
            gene_distribution[gene] /= gene_sum
        # 归一化特征分布
        for trait in trait_distribution:
            trait_distribution[trait] /= trait_sum

学习链接

参考代码链接:https://github.com/wbsth/cs50ai
https://github.com/PKUFlyingPig/cs50_ai
视频链接(b站中文机翻字幕): https://www.bilibili.com/video/BV1AQ4y1y7wy/?p=5&vd_source=23b9ed7e58fa7e510011caaf2e7e3320
课程官网(全套资源):https://cs50.harvard.edu/ai/2023/

总结

这次作业我感觉和课程结合的不是很好,不过也大致利用了马尔科夫与贝叶斯的一些思想,比较浅显

posted @ 2023-09-15 15:55  dyccyber  阅读(74)  评论(0编辑  收藏  举报