python搭建 ADLINE 网络判断男女

python搭建 ADLINE 网络判断男女,第1张

python搭建 ADLINE 网络判断男女 小记

f ( x ) = σ ( w x + b ) f(x)=\sigma(wx+b) f(x)=σ(wx+b) 该模型叫做ADLINE(Adative Linear Neuron,自适应线性神经元) ,1960年发明的

2022.4.8 仿照 张觉非的《用Python实现深度学习框架》的第二章搭建

题目

训练一个模型, 给出一个人的身高、体重和体脂率,判断男女。


判断思路

男性的label=1,女性label=-1,

得出的predict,predict=1判断为男性,predict=-1判断为女性

搭建难点
  1. 树形结构,层层递归的理解

  2. 反向传播时,计算每个父节点对子节点的雅克比矩阵,这时矩阵求导是个难点。


    比如C=step(A),求C对A的导数, C=AB, C对A对导数。


注意事项
  1. 每次训练完毕后要把jacobi和value置为None,因为如果不置None,就不会去计算新的值

  2. 矩阵乘法顺序,参见机器学习中的https://www.cnblogs.com/pinard/p/10825264.html矩阵向量求导(四) 矩阵向量求导链式法则 - 刘建平Pinard - 博客园 (cnblogs.com) 。



    z = f ( y ) , y = x w ,   ∂ z ∂ x = ∂ z ∂ y w T , ∂ z ∂ w = x T ∂ z ∂ y , ( 对 左 边 求 导 , 它 的 导 数 放 在 右 边 ; 对 右 边 求 导 , 它 的 导 数 放 在 左 边 ) z=f(y),y=xw,\ \frac{\partial z}{\partial x}=\frac{\partial z}{\partial y}w^T, \ \frac{\partial z}{\partial w}=x^T\frac{\partial z}{\partial y}, \(对左边求导,它的导数放在右边;对右边求导,它的导数放在左边) z=f(y),y=xw, xz=yzwT,wz=xTyz,

代码框架

代码 main.py
import numpy as np
from icecream import ic
import node
import ope
import loss
from graph import  default_graph

def make_test():
    # 生产测试数据
    m_h = np.random.normal(171, 6, 500)
    f_h = np.random.normal(168, 5, 500)
    m_w = np.random.normal(70, 10, 500)
    f_w = np.random.normal(57, 8, 500)
    m_bfrs = np.random.normal(16, 2, 500)
    f_bfrs = np.random.normal(22, 2, 500)

    m_labels = [1] * 500
    f_labels = [-1] * 500

    train_set = np.array([np.concatenate((m_h, f_h)),
                          np.concatenate((m_w, f_w)),
                          np.concatenate((m_bfrs, f_bfrs)),
                          np.concatenate((m_labels, f_labels))
                          ]).T

    np.random.shuffle(train_set)
    return  train_set



if __name__=='__main__':
    train_set=make_test()
    x=node.Variable(shape=(3,1))
    w=node.Variable(shape=(1,3))
    b=node.Variable(shape=(1,1))
    label=node.Variable(shape=(1,1))

    w.set_value(np.mat(np.random.normal(0,0.001,(1,3))))
    b.set_value(np.mat(np.random.normal(0,0.001,(1,1))))

    y=ope.Add(ope.MatMul(w,x),b)
    loss=loss.PerceptionLoss(ope.MatMul(label,y))
    predict=ope.Step(y)
    learning_rate=0.001


    for epoch in range(100):
        for data in train_set:
            # 输入数据
            x.set_value(np.mat(data[:-1]).T)
            label.set_value(np.mat(data[-1]))
            loss.forward()
            w.backward(loss)
            b.backward(loss)
            # 想优化的结点都是标量结点,即都是1*n,
            w.set_value(w.value - learning_rate * w.jacobi.T.reshape(w.shape()))
            b.set_value(b.value - learning_rate * b.jacobi.T.reshape(b.shape()))
            default_graph.clear_jacobi()  #要清理每一次的雅可比矩阵,否则递归时会因为雅可比矩阵含有值而不重新计算


        if epoch%10==0:
            pred = []
            for data in train_set:
                # 输入数据
                x.set_value(np.mat(data[:-1]).T)
                label.set_value(np.mat(data[-1]))
                predict.forward()
                pred.append(predict.value[0, 0])

            pred = np.array(pred) * 2 - 1
            accuracy = (train_set[:, -1] == pred).astype(np.int).sum() / len(train_set)
            print("训练次数为:",epoch,"时,准确率为:",accuracy)
            ic(epoch,accuracy)
node.py
# node.py
from  graph import default_graph
import numpy as np
from abc import ABC, abstractmethod
from icecream import ic

class Node(object):
    def __init__(self,*parents):
        #把结点加入默认的图
        self.graph=default_graph
        self.graph.add_node(self)

        #性质
        self.parents=list(parents)
        self.children=[]
        self.value=None
        self.jacobi=None

        for parent in self.parents:
            parent.children.append(self)


    @abstractmethod
    def compute(self):
        """
               抽象方法,根据父节点的值计算本节点的值
               在前向传播时使用
        """
        pass

    @abstractmethod
    def get_jacobi(self,parent):
        # ic(type(self))
        """
             抽象方法,计算本节点对某个父节点的雅可比矩阵
             在反向传播时用
             """
        pass

    #以下为node的通用函数
    def dimension(self):
        assert self.value is not None
        return self.value.shape[0]*self.value.shape[1]

    def shape(self):
        assert self.value is not None
        return  self.value.shape

    def clear_jacobi(self):
        self.jacobi=None

    def reset_value(self, recursive=True):
        self.value = None
        if recursive:
            for child in self.children:
                child.reset_value()

    def forward(self):
        for parent in self.parents:
            if parent.value is None:
                parent.forward()

        self.compute()

    def backward(self,result):
        if self.jacobi is None:
            if self is result:
                self.jacobi = np.mat(np.eye(self.dimension()))
            else:
                self.jacobi = np.mat(
                    np.zeros((result.dimension(), self.dimension())))

                for child in self.children:
                    # 这里有个乘法顺序要注意,因为都是对w求导且w在左边(y=wx+b),故上个结点对该结点的导数是在右边的
                    if child.value is not None:
                        self.jacobi += child.backward(result) * child.get_jacobi(self)

        return self.jacobi



class Variable(Node):
    def __init__(self,shape,**kwargs):
        Node.__init__(self, **kwargs)
        self.size=shape



    def set_value(self,value):
        assert isinstance(value, np.matrix)
        # 注意Variable的形状是在定义的时候就设定好的,故而使用self.size
        assert self.size ==value.shape
        self.reset_value()
        self.value=value
ope.py
from node import Node
import numpy as np
from icecream import ic
class Operator(Node):
    pass

class Add(Operator):
    def compute(self):
        assert self.parents is not None

        self.value=np.mat(np.zeros(self.parents[0].shape()))
        for parent in self.parents:
            assert self.shape()==parent.shape()
            self.value+=parent.value

    def get_jacobi(self, parent):
        return np.identity(parent.dimension())



class MatMul(Operator):
    def compute(self):
        assert len(self.parents)==2


        assert self.parents[0].shape()[1] ==self.parents[1].shape()[0]
        self.value=self.parents[0].value *self.parents[1].value

    def get_jacobi(self,parent):
        # 矩阵乘法的雅可比矩阵在书41页
        zeros = np.mat(np.zeros((self.dimension(), parent.dimension())))
        if parent is self.parents[0]:
            return fill_diagonal(zeros, self.parents[1].value.T)

        elif parent is self.parents[1]:
            """
               原理比较复杂,见书P44,最后是通过重排列行和列的索引方式实现的
            """
            jacobi = fill_diagonal(zeros, self.parents[0].value)
            row_sort = np.arange(self.dimension()).reshape(self.shape()[::-1]).T.ravel()
            col_sort = np.arange(parent.dimension()).reshape(parent.shape()[::-1]).T.ravel()
            return jacobi[row_sort, :][:, col_sort]

class Step(Operator):
    def compute(self):
        assert len(self.parents)==1
        self.value=np.mat(np.where(self.parents[0].value>0.0,1.0,0.0))

    def get_jacobi(self,parent):
        """
        因为在这个代码离,Step只用于得到预测值,故而不用对其反向传播求导就不算了
        """
        pass


# 一些函数补充

def fill_diagonal(to_be_filled, filler):
    """
    将 filler 矩阵填充在 to_be_filled 的对角线上
    """
    assert to_be_filled.shape[0]/filler.shape[0] == to_be_filled.shape[1]/filler.shape[1]
    h,w=filler.shape
    n=to_be_filled.shape[0]//filler.shape[0]
    for i in range(n):
        to_be_filled[i*h:(i+1)*h,i*w:(i+1)*w]=filler

    return to_be_filled
loss.py
from node import Node
import numpy as np
from icecream import ic
class LossFunction(Node):
    pass


class PerceptionLoss(LossFunction):
    """
        感知机损失,输入为正时为0,输入为负时为输入的相反数
    """
    def compute(self):
        x=self.parents[0].value
        self.value=np.mat(np.where(x>=0.0, 0.0, -x))


    def get_jacobi(self,parent):
        """
               雅克比矩阵为对角阵,每个对角线元素对应一个父节点元素。


若父节点元素大于0,则 相应对角线元素(偏导数)为0,否则为-1。


""" diag = np.where(parent.value >= 0.0, 0.0, -1) return np.diag(diag.ravel())

graph.py

class Graph():
  def __init__(self):
      self.nodes=[]

  def clear_jacobi(self):
      for node in self.nodes:
          node.clear_jacobi()

  def add_node(self,node):
      self.nodes.append(node)


# 全局默认计算图
default_graph = Graph()
训练结果

训练次数为: 10 时,准确率为: 0.831
训练次数为: 20 时,准确率为: 0.929
训练次数为: 30 时,准确率为: 0.912
训练次数为: 40 时,准确率为: 0.938
训练次数为: 50 时,准确率为: 0.939
训练次数为: 60 时,准确率为: 0.941
训练次数为: 70 时,准确率为: 0.942
训练次数为: 80 时,准确率为: 0.93
训练次数为: 90 时,准确率为: 0.93

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/567690.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-09
下一篇 2022-04-09

发表评论

登录后才能评论

评论列表(0条)

保存