python搭建 ADLINE 网络判断男女

python搭建 ADLINE 网络判断男女

笔记

python搭建 ADLINE 网络判断男女该模型叫做ADLINE(Adative Linear Neuron,自适应线性神经元) ,1960年发明的

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

话题

训练一个模型,给出一个人的身高、体重和体脂百分比来判断男性和女性。

判断思路

mark

男性的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)。
    python搭建 ADLINE 网络判断男女

代码框架

mark

代码

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

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
乘风的头像乘风管理团队
上一篇 2022年4月9日 下午1:44
下一篇 2022年4月9日 下午1:54

相关推荐