《零基础入门深度学习》学习笔记(一)感知器-代码详解

本文介绍了深度学习的基础——感知器,详细解析了感知器的Python实现,包括权重初始化、激活函数、预测及训练过程。通过实例演示了如何使用感知器实现逻辑与(AND)函数,并解释了模型参数的更新规则。通过逐步学习,读者可以掌握深度学习的初步知识。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近在搜集资料时无意中看到知乎中有人推荐此教程,特整理输出一遍。形成此系列文章。本文原文标题为:

《零基础入门深度学习(1) - 感知器》

原理部分请直接参考原文:目前只分析源码:此代码修改原码,为python3环境。

from functools import reduce

class Perceptron(object):
    def __init__(self, input_num, activator):
        """
            初始化感知器,设置输入参数的个数,以及激活函数。
            激活函数的类型为double -> double。
        """
        self.activator = activator
        # 权重向量初始化为0。
        self.weights = [0.0 for _ in range(input_num)]
        # 偏置项初始化为0
        self.bias = 0.0
        
    def __str__(self):
        """
            打印学习到的权重、偏置项
        """
        return "weights\t:%s\nbias\t:%f\n" % (self.weights, self.bias)
    
    def predict(self, input_vec):
        """
            输入向量,输出感知器的计算结果。
        """
        # 把input_vec[x1, x2, x3, ...]和weights[w1, w2, w3, ...]打包在一起。
        # 注释:原文当中zip放在python3环境中,不能跑通。
        # python3中zip函数返回的值类型为zip。惰性迭代了。无法满足map的需求。
        # 变成[(x1, w1), (x2, w2), (x3, w3), ...]
        # 然后利用map函数计算[x1*w1, x2*w2, x3*w3,...]
        # 最后利用reduce求和。
        return self.activator(reduce(lambda a, b: a+b, map(lambda x, w : x * w, input_vec, self.weights), 0.0) + self.bias)
#        return self.activator(reduce(lambda a, b: a+b, map(lambda (x, w) : x * w, zip(input_vec, self.weights)), 0.0) + self.bias)

    def train(self, input_vecs, labels, iteration, rate):
        """
            输入训练数据:一组向量、与每个向量对应的label;以及训练轮数、学习率
        """
        for i in range(iteration):
            print("第 %s 轮训练:" % (i+1))
            self._one_iteration(input_vecs, labels, rate)
            
    def _one_iteration(self, input_vecs, labels, rate):
        """
            一次迭代,把所有的训练数据过一遍
        """
        # 把输入和输出打包在一起,成为样本的列表[(input_vec, label), ...]
        # 而每个训练样本是(input_vec, label)
        samples = zip(input_vecs, labels)
        # 对每个样本, 按照感知器规则更新权重
        for (input_vec, label) in samples:
            # 计算感知器在当前权重下的输出。
            output = self.predict(input_vec)
            # 更新权重
            self._update_weights(input_vec, output, label, rate)
    
    def _update_weights(self, input_vec, output, label, rate):
        """
            按照感知器规则更新权重。
        """
        # 把input_vec[x1, x2, x3, ...]和weights[w1, w2, w3, ...]打包在一起。
        # 变成[(x1, w1), (x2, w2), (x3, w3),...]
        # 然后利用感知器规则更新权重
        delta = label - output     # 这一步就是更新权重的依据,如果不为零更新,为零不更新。
        self.weights = list(map(lambda x, w: w + rate * delta * x, input_vec, self.weights)) 
#         self.weights = list(map(lambda (x, w): w + rate * delta * x, zip(input_vec, self.weights)))
        # 此处与原文也不相同,原因是python3中map函数返回的是一个map类型的序列,而不是列表了。类似于zip。
#         print(type(self.weights))
        # 更新bias
        self.bias += rate * delta
        print(delta, self.weights, self.bias)  # 观察delta的值,和weight和bias的值是否按照规则更新。

# 接下来,我们利用这个感知器类去实现and函数。 

def f(x):
    """
        定义激活函数f
    """
    return 1 if x > 0 else 0

def get_training_dataset():
    """
        基于and真值表构建训练数据。
    """
    # 构建训练数据
    # 输入向量列表 
    
    input_vecs = [[1,1], [0, 0], [1, 0], [0, 1]]
    # 期望的输出列表,注意要与输入一一对应
    # [1,1] -> 1, [0, 0] -> 0, [1, 0] -> 0, [0, 1] -> 0
    labels = [1, 0, 0, 0]
    return input_vecs, labels

def train_and_percetron():
    """
        使用and真值表训练感知器。
    """
    # 创建感知器,输入参数个数为2,因为and的是二元函数。激活函数为f
    p = Perceptron(2, f)   # 示例化对象。
    # 训练, 迭代10轮, 学习速率为0.1
    input_vecs, labels = get_training_dataset()
    p.train(input_vecs, labels, 10, 0.1)
    # 返回训练好的感知器。
    return p

if __name__=="__main__":
    # 训练and感知器
    and_perceptron = train_and_percetron()
    # 打印训练获得的权重
    print(and_perceptron)
    # 测试
    print("1 and 1 = %d" % and_perceptron.predict([1, 1]))
    print("0 and 0 = %d" % and_perceptron.predict([0, 0]))
    print("1 and 0 = %d" % and_perceptron.predict([1, 0]))
    print("0 and 1 = %d" % and_perceptron.predict([0, 1]))

输出: 

weights	:[0.1, 0.2]
bias	:-0.200000

1 and 1 = 1
0 and 0 = 0
1 and 0 = 0
0 and 1 = 0

第一步:

从背景开始分析:输入层维度:数据的特征维度,因为是and是二元函数,二维。所以输入层x维度为2。或者说两个神经元。那么相应的系数w也应该是两个。系数也是二维。这样就有了上面代码中:
第12行:self.weights = [0.0 for _ in range(input_num)]。w和x都有了,偏置b为共享,所以只有一个,所有参数初始设置为0.0。

第二步:

拟合的本质就是wx+b=y,然后真实标签值t与预测值y的差值来调节w和b的值。使y和t努力接近。带入训练数据每次更新模型参数。模型参数调整策略:

因此公式对应63行的:w + rate * delta * x和67行:self.bias += rate * delta。其中61行中:delta = label - output。即t-y。rate为学习率。

第三步:

每个样本依次输入到模型(将x与w和b运算,通过激活函数输出)然后每一次都会执行预测predict函数,(第一次通过初始化参数的w和b计算,后续数据通过上面规则(_update_weights函数)调整的w和b来计算)。不断循环,直到迭代次数结束。所以每一次的_one_iteration函数执行两步:第一步:predict,第二步:根据t-y来更新w和b的_update_weights函数。因为没有考察的目标函数,所以不会有计算loss之类的操作。

这个循环执行的过程放在了train函数中执行。通过for 将iteration次数的迭代用完。

第一个问题需要反思:模型到底是什么?

第一运算规则:第二规则中的计算用到的系数

运算规则就是用w和输入x相乘加b,然后激活输出,定义了模型的操作结构,后续一般称为网络结构:到底是CNN还是RNN。同时网络结构又细分LeNet和resNet等等。。都是运算规则。

运算中用到的计算,这些系数,就是模型的参数。

例子:最简单的说拟合直线:y = kx + b。规则是直线,所以我们定义一维直线公式。此公式中有两个参数需要计算k和b。

第二个问题需要反思:模型在哪里?这么抽象,code中在哪里?

网络模型:一般有模型网络结构的会单独建立一个文件,告诉我们运算规则。网络有多少层,每一层有哪些操作。数据输入后要怎么进入网络模型。

参数模型:计算的参数在哪里?本例中,我们仔细思考,其实定义类的时候,在__init__中,将参数以类属性的形式保存下来,在内存中开辟了空间。等着我们运算和使用。最后通过不断更新,这些对象的属性值发生变化。

总之:参数一直保存在类实例化之后对象的属性值中。这个值导出来,就是模型参数文件。训练实际上是更新对象的属性值w和b。

第三个问题:在哪里结合?模型怎么用的。

在predict中结合。predict中将x的值,带入模型,通过与参数结合按照模型运算规则进行运算。

这个predict即在训练时应用运算规则。也要在模型最终确定后用来工作,即测试集测试结果。第二段代码中:38到41。做的事情,也是我们真正使用模型的操作。


代码中还有一点值得借鉴:连输入都用函数来执行,是分的真利索。 真clean code。


本文最后:引用原文中一段经典话:

值得高兴的是,你终于已经走出了深度学习入门的第一步,这是巨大的进步;坏消息是,这仅仅是最简单的部分,后面还有无数艰难险阻等着你。不过,你学的困难往往意味着别人学的也困难,掌握一门高门槛的技艺,进可糊口退可装逼,是很值得的。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值