python vector 初始化_从零开始搭建机器学习算法框架(python)--计算框架

介绍

今天开始一个新的系列,这个系列的目标是用python在不使用任何第三方库的情况下去实现各类机器学习或者深度学习的算法。之所以会有这种想法是因为每当我想提高编程技巧的时候,我总希望能够做一些简单又有趣的小项目练手。我一直对机器学习算法颇感兴趣,所以我想为什么不用python从零开始搭建一套迷你机器学习库呢。于是我尝试这么做了,这个系列就是记录我实现这一想法的过程。另外,由于这个项目很少用到第三方库,而且实现上尽可能抱着语法简单,因此也较为容易转换成其他语言。

说起来,在没有开始这个项目之前,有些东西,比如Numpy里的array赋值、取值、转置这些,用起来跟呼吸一样自然,并造成一种...就像被问1+1为什么等于2的感觉:它就是应该等于2没有为什么。但真正深入实现的时候,发现又不是这么一回事...

本篇是系列的第一篇,主要是模仿numpy的部分功能,搭建一个矩阵计算框架。当然,这个实现不会像商业库那样拥有强大的功能以及稳定性,因而会有些不那么robust。但对于抱着学习的目的来说,忽略一些复杂情况可以更容易理解本质。我打打算是每一期的代码都是最简实现,够用就行,只有后面实现算法时需要用到新功能时才会新增功能。那么下面就开始这个矩阵计算框架的第一步,Vector类。

1.Vector类

矩阵运算首先得要有矩阵,numpy里面矩阵的展现形式是ndarray这个类,pytorch或者tensorflow都叫Tensor。我这里起个名字叫Vector吧,用于存储矩阵的数据结构。

1.1 初始化函数

关于Vector类,有两个必不可少的类成员属性,一是用于存储数值的变量array,二是用于表示矩阵形状的变量shape。

总之,我们希望如果对Vector进行索引的话,得到的东西还是个vector。所以最简单的想法就是,array里装的是低一维的Vector。这样索引的时候直接可以对array索引直接取到响应的Vector了。这样,array在初始化的时候需要做一些额外的操作,直接看代码好了:

class Vector:def __init__(self, data, shape=None, requires_grad=False, grad=None, _creator=None, name='Unknown'):self.name = nameif not shape:self.shape = _inference_shape(data)else:self.shape = deepcopy(shape)if self.ndim > 1:self.array = [v if isinstance(v, Vector) else Vector(v, shape=self.shape[1:]) for v in data]else:self.array = [cast(v) for v in data]self.grad = gradself.requires_grad = requires_gradself._creator = _creator

上述代码看到,初始化Vector最主要是两个参数,一个是data,即存了什么样的数据。另一个是shape,表明了数据按照什么样的格式存储的(其他参数是自动求导所需要的,后一期再做介绍)。需要注意的是,如果data本身具备多维的结构,比如嵌套的list,那么shape可以为None,此时,shape可以从data的嵌套结构中推测出来,即_inference_shape()这个函数。该函数的作用是给定一个多维list,并返回这个list的shape,具体实现放在了后面。

接下来,如果data是一个高维的结构,那么array里存的是低一维度的Vector,因此可以通过递归构造Vector。当data是一维的时候,意味着array里存的是数值,直接把data存入array就好了。这里需要注意的是我为python中的int以及float分别新建了一个类Int以及Float。这么做的原因说主要是想把value的传递方式从按值传递转变为引用传递。考虑到当vector进行转置操作时会新生成一个vector,但是我们希望对转置后的vector进行修改时,原始vector的值也跟着修改,毕竟他俩是同一个东西,只不过shape变了而已。如果使用原始数据类型的话,赋值时会按值传递的,也就无法实现这一效果。

题外话:关于变量array存储数据的形式,我在这里还踩了两个坑。一开始我很天真,以为多维矩阵就是list的嵌套,即Vector的array就是诸如:[[1,2,3],[4,5,6]]。但是随着进度继续,我发现单纯list的嵌套会有很多麻烦的地方。其中第一不和谐的点是,如果array为list的嵌套,对Vector的某个维度进行索引的时候,取出来是个list,而不是一个Vector。尽管在复写__getitem__的时候,可以构造一个新的Vector实例,但每次索引都要初始化一个Vector,这会影响索引时的速度...当然,list作为线性表(当然python里没有严格的线性表,list是线性表和链表的结合),索引速度是很快的,于是我打算让Vector类在索引时尽可能维持线性表的特性,于是产生了我第二个想法。第二个想法有点极端:我让array的存储结构就是一个一维的list。对vector取值时,可以根据shape把index进行映射到array相应的index上,来达到索引指定位置的目的,思路类似下图(图一,图一中展示了一个三维矩阵,当索引(0,2,0)这个位置时,可以通过简单的换算把(0,2,0)转换成list上第5个位置)。这种实现有很多方便的地方,比如做element-wise的运算时。但是,在写转置操作时,我觉得这种方案非常难写,于是放弃了这个方案。

14103d8fe6b706590b9e78dd3dd56509.png
图一

1.2 数据索引取值

python中如果想实现对某个类的实例进行索引取值,就是重写__getitem__方法。类似numpy,索引支持切片,即vector[1:3],以及多维度索引,vector[1,2,3]。

    def __getitem__(self, index):def recursive_getitem(vector, index_):if len(index_) > 0:res_array_ = []if isinstance(index_[0], slice):for elem in vector.array[index_[0]]:res_array_.append(recursive_getitem(elem, index_[1:]))else:res_array_ = recursive_getitem(vector.array[index_[0]], index_[1:])else:return vectorreturn res_array_if isinstance(index, int):return self.array[index]elif isinstance(index, slice):start = index.start if index.start else 0end = index.stop if index.stop else self.shape[0]step = index.step if index.step else 1res_array = [self[i].array if isinstance(self[i], Vector) else self[i] for i in range(start, end, step)]elif isinstance(index, tuple) or isinstance(index, list):if len(index) < self.ndim:index = list(index)index.extend([slice(None, None, None)] * (self.ndim - len(index)))res_array = recursive_getitem(self, index)else:raise Exception()if isinstance(res_array, list):return Vector(res_array)else:return res_array

__getitem__有一个入参index,表示索引的位置。由于既要支持普通索引,又要支持切片操作同时还要支持高维索引,所以index的类型有三种情况,第一个是最普通的,传入一个int型数值,第二种传进来的是slice。前两种都是在单一维度上的索引。第三种情况传进来的是一个元祖(或list),表示在多个维度上的索引。最后一个情况处理起来稍微麻烦些,不过思路却很简单:由于需要在多个维度上索引,很容易想到按照顺序每次只处理一个维度,这样高维索引问题可以转化为多次一维的索引。用递归很容易解决,只是需要留意下高维索引和切片索引结合的情况,即vector[1,:,3]。

1.3 索引赋值

关于赋值,我犯过一个错误,就是想当然的认为,既然有了索引,那么赋值不就很简单吗,__setitem__里直接self[key] = value就完事了。但实际上,=操作就是__setitem__,如果__setitem__写上self[key] = value,那么就等于是循环调用一个没有任何意义的__setitem__,结果就是死循环。不过,__getitem__写好了的话,__setitem__也不算特别麻烦:

    def __setitem__(self, key, value):t = self[key]if isinstance(t, BaseType):v = cast(value)t.val = v.valelif isinstance(t, Vector):index = [0 for _ in range(t.ndim)]while 1:for axis in range(t.ndim - 1):if index[axis] == t.shape[axis]:index[axis] = 0index[axis + 1] += 1if index[-1] >= t.shape[-1]:breakt[index].val = cast(value[index]).valindex[0] += 1

与取值类似,key(对应于__getitem__里的index,原谅我命名没有统一...)有三种情况(上面代码漏写了一种情况)。由于索引取值已经在__getitem__里实现了,剩下的只是赋值而已。这里也只是提醒一点,如果value是python内置的数据类型需要转化成自定义的Int或Float。

1.4 转置与交换维度

接下来是一个比较重要的功能,转置。说实话,可能是我比较反应比较迟钝,转置这个操作卡了我很长时间。因为我始终觉得转置其实就是几个轴交换顺序了,所以我一直想的是如何给Vector加上轴的概念(类似于图一的结构)这样转置会变得异常简单。但真正做下去以后,总觉得加轴反而会把Vector的结构弄复杂了,于是放弃了这个思路。我现在的实现方式如下:

    def transpose(self, axises=None):new_shape = [self.shape[axises[i]] for i in range(self.ndim)]new_vec = zeros(new_shape)curr_index = [0 for _ in range(self.ndim)]target_idx = [0 for _ in range(new_vec.ndim)]while 1:for axis in range(self.ndim - 1):if curr_index[axis] == self.shape[axis]:curr_index[axis] = 0curr_index[axis + 1] += 1if curr_index[-1] >= self.shape[-1]:breakfor i, j in enumerate(axises):target_idx[i] = curr_index[j]new_vec[target_idx] = self[curr_index]curr_index[0] += 1return new_vec

transpose接收一个参数axises,表示维度的交换顺序:原始的维度为[0,1,2,3],如果打算交换第0维与第2维的位置,则axises为[2,1,0,3]。我这里的实现思路可能不够高效,但好在还算简单直白,首先用转置后的shape初始化一个新的vector,然后遍历原始vector,把数值一个个塞到新的vector里去。遍历的过程比较原始,就是通过构造多维索引curr_index,从最低位开始遍历,每次循环在最低位+1,不过需要注意“进位”。另外target_idx 是转置后对curr_index的映射。

另外,numpy里还有个swapaxes函数,功能类似transpose,但是只接受两个参数,即只能交换两个维度。我这里用transpose来辅助实现的。

    def swapaxes(self, axis1=None, axis2=None):axises = [i for i in range(self.ndim)]axises[axis1], axises[axis2] = axises[axis2], axises[axis1]return self.transpose(axises)

1.5 To String

还有一个重要的功能是,我希望写好的Vector类能够顺利的被print出来。在python中,如果想要把一个类print出来,可以覆写__str__方法。听起来这个功能似乎很简单,实际上如果想要输出的工整一些,还是有些麻烦的:

    def __str__(self):max_l = len(str(self.max_num))def recursive_print_vector(vector, index=0):if isinstance(vector, Vector):res = []for i, elem in enumerate(vector.array):s = recursive_print_vector(elem, i)if isinstance(vector.array[0], Vector):if i == len(vector.array) - 1:res.append(str(s))else:res.append('%sn' % s)else:offset = max_l - len(s)prefix = ' ' * offsetres.append('%s%s' % (prefix, s))blank = ' ' * (self.ndim - vector.ndim - 1) if index else ''return blank + '[' + ' '.join(res) + ']'else:return str(vector)return recursive_print_vector(self)

另外上述函数需要计算Vector的最大值:

    @propertydef min_num(self):min_num = float('inf')index = [0 for _ in range(self.ndim)]while 1:for axis in range(self.ndim - 1):if index[axis] == self.shape[axis]:index[axis] = 0index[axis + 1] += 1if index[-1] >= self.shape[-1]:breakval = self[index].valif min_num < val:min_num = valindex[0] += 1return min_num@propertydef max_num(self):max_num = float('-inf')index = [0 for _ in range(self.ndim)]while 1:for axis in range(self.ndim - 1):if index[axis] == self.shape[axis]:index[axis] = 0index[axis + 1] += 1if index[-1] >= self.shape[-1]:breakval = self[index].valif max_num > val:max_num = valindex[0] += 1return max_num

1.6 增加维度

unsqueeze的功能是给vector增加一个维度,这个比较简单,核心就是对vector进行遍历。另外看代码的话,似乎很多函数都用到了对vector进行循环遍历的功能,看来这块代码可以封装起来...

    def unsqueeze(self, dim):if dim == -1:dim = self.ndimnew_shape = deepcopy(self.shape)new_shape.insert(dim, 1)new_vec = zeros(new_shape)index = [0 for _ in range(new_vec.ndim)]while 1:for axis in range(new_vec.ndim - 1):if index[axis] == new_vec.shape[axis]:index[axis] = 0index[axis + 1] += 1if index[-1] >= new_vec.shape[-1]:breakcurr_index = deepcopy(index)del curr_index[dim]new_vec[index] = self[curr_index]index[0] += 1return new_vec

1.7 减少维度

有unsqueeze就有squeeze。

    def squeeze(self, dim):if self.shape[dim] == 1:if dim == -1:dim = self.ndim - 1new_shape = deepcopy(self.shape)del new_shape[dim]new_vec = zeros(new_shape)index = [0 for _ in range(new_vec.ndim)]while 1:for axis in range(new_vec.ndim - 1):if index[axis] == new_vec.shape[axis]:index[axis] = 0index[axis + 1] += 1if index[-1] >= new_vec.shape[-1]:breakcurr_index = deepcopy(index)curr_index.insert(dim, 0)new_vec[index] = self[curr_index]index[0] += 1return new_vecelse:raise ValueError

2.若干工具函数

_inference_shape:功能是推断嵌套list的shape。

def _inference_shape(data):shape = []while isinstance(data, list):shape.append(len(data))data = data[0]return shape

create_array_by_shape:与_inference_shape功能相反,是通过shape构造嵌套的list。

def create_array_by_shape(shape, val):if isinstance(shape, int):return valelse:new_shape = shape[1:] if len(shape) > 1 else shape[0]return [create_array_by_shape(new_shape, val) for _ in range(shape[0])]

cast:用于转换类型的函数

def cast(v):if isinstance(v, int):return Int(v)elif isinstance(v, float):return Float(v)elif isinstance(v, BaseType):return velse:raise ValueError

zeros和ones:构造一个0或1组成的Vector实例。

def zeros(shape):array = create_array_by_shape(shape, 0)return Vector(array, shape=shape)def ones(shape):array = create_array_by_shape(shape, 1)return Vector(array, shape=shape)

结语

至此,一个简单Vector类别就构造好了,它现在可以索引,可以赋值,以及能够进行转置操作。作为一个矩阵计算框架,功能似乎还不够多。更多的功能会在后面需要的时候一点点补充。在下期我打算实现自动求导,以及大部分运算操作。

https://github.com/iron-fe/machine_learning_toys.git​github.com

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/459192.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

windows phone 页面主题设计

达到如图这个效果&#xff1a; 1.保证状态栏背景色与主题栏颜色相同需设置状态栏的透明度&#xff0c;代码如下&#xff1a;shell:SystemTray.IsVisible"True" shell:SystemTray.Opacity"0.01" 2.顶部状态栏高度为25&#xff0c;字的上下要留部分空白3.内容…

JAVA如何才能导出这样的EXCEL?

2019独角兽企业重金招聘Python工程师标准>>> 最近的项目有个需求&#xff0c;需要做个报表&#xff0c;excel如上所示。没有很好的办法&#xff0c;求指教。 转载于:https://my.oschina.net/secret620/blog/611450

Xcode 修改工程名以及注意事项

1、先把整个工程文件夹名改为新的工程名。 2、打开工程&#xff0c;单击&#xff0c;输入新的工程名,会出现&#xff0c;点击确定。 3、回到工程界面&#xff0c;在中选择 Manage Schemes,然后再弹出的对话框&#xff0c;把工程名改为新的名字。 4、最好在工程中&#xff0c;把…

脚本命令配置mysql_MySQL 自动化部署脚本

一、环境说明操作系统&#xff1a;CentOS数据库版本&#xff1a;MySQL 5.7/8.0参数&#xff1a;buffer pool 会根据系统内存指定、默认双一、GTID、SlowLog脚本默认安装路径&#xff1a;/usr/local/mysql脚本默认数据路径&#xff1a;/data/mysql*(根据安装包版本适应 比如 5.7…

第2章 数字之魅——快速寻找满足条件的两个数

快速寻找满足条件的两个数 问题描述 能否快速找出一个数组中的两个数字&#xff0c;让这两个数字之和等于一个给定的数字&#xff0c;为了简化起见&#xff0c;我们假设这个数组中肯定存在这样一组或以上符合要求的解。 分析与解法 【解法一】 代码如下&#xff1a; 1 package …

eigen 列向量转矩阵_快速入门矩阵运算——开源库Eigen

矩阵是数学中一个重要的工具&#xff0c;广泛应用于各种场景下的数值分析&#xff0c;例如&#xff0c;数字信号处理&#xff0c;图像处理等。我们如何在程序中使用矩阵进行运算呢&#xff1f;本文将为大家介绍一个开源的矩阵运算工具——Eigen。Eigen is a C template library…

mysql raid_DBA们应该知道的RAID卡知识_MySQL

bitsCN.com对于数据库这种特殊应用IOphotoshop/ target_blank classinfotextkey>PS往往会成为瓶颈&#xff0c;突破的这个瓶颈的有效方法不多&#xff0c;软件方面主要是读写分离&#xff0c;垂直拆分&#xff0c;分区表技术&#xff0c;cluster。硬件方面主要是raid&#x…

基于Maven的SSH框架搭建

2019独角兽企业重金招聘Python工程师标准>>> 1.工程介绍 工程是结合了Springstruts2hibernate&#xff0c;实现了一个简单的form表单提交的功能&#xff0c;可能需要对spring&#xff0c;struts2&#xff0c;hibernate有一个基础的了解才好理解。 2.工程结构图 首先…

交通警察手势信号(动画演示)

*************************************************** 更多精彩&#xff0c;欢迎进入&#xff1a;http://shop115376623.taobao.com *************************************************** 一、交通警察手势信号&#xff0d;停止信号 二、交通警察手势信号&#xff0d;直行信…

mysql和mybatis面试题_BATJ面试题汇总详解:MyBatis+MySQL+Spring+Redis+多线程

SpringSpring 概述什么是spring?使用Spring框架的好处是什么&#xff1f;Spring由哪些模块组成&#xff1f;解释AOP模块Spring配置文件什么是Spring IOC 容器&#xff1f;依赖注入什么是Spring的依赖注入&#xff1f;有哪些不同类型的IOC(依赖注入)方式&#xff1f;哪种依赖注…

Codeblocks和gdb调试 (转)

*************************************************** 更多精彩&#xff0c;欢迎进入&#xff1a;http://shop115376623.taobao.com *************************************************** 使用C::B和gdb调试是一件简单的事情。下面&#xff0c;让我们调试一个简单的循环&…

mysql5.7.17 win7_win7下mysql5.7.17安装配置方法图文教程

win7下安装mysql5.7.17图文教程&#xff0c;分享给大家。1.下载安装包请在以下有zip包和msi两种类型包&#xff0c;建议新手选择zip包下载&#xff0c;有助于熟习mysql2.解压mysql压缩包下载完成后解压&#xff0c;将其放在要安装的目录下面&#xff0c;如&#xff1a;e:\mysql…

停一下

15年过去已半载有余&#xff0c;回头看年初定下的目标&#xff0c;有种管中窥豹的感觉。之前和肉山讨论的时候&#xff0c;他对我想要发展的方向并没有表示赞同。 现在认为他是对的&#xff0c;发展的方向太靠前了&#xff0c;ui&#xff0c;canvas&#xff0c;svg&#xff0c;…

which 命令

我们经常在linux要查找某个文件&#xff0c;但不知道放在哪里了&#xff0c;可以使用下面的一些命令来搜索&#xff1a; which 查看可执行文件的位置。 whereis 查看文件的位置。 locate 配合数据库查看文件位置。 find 实际搜寻硬盘查询文件名…

18ch

18.2 线程和进程 18.2.1 什么是进程&#xff1f; 18.2.1 什么是进程&#xff1f; 计算机程序只不过是磁盘中可执行的&#xff0c;二进制的数据。它们只有在被读取到内存中&#xff0c;被操作系统调用的时候才开始它们的生命周期。进程&#xff08;重量级进程&#xff09;是程序…

安卓四大组件总览

在安卓中四大组件 &#xff08;Component&#xff09;指的是&#xff1a;Activity&#xff0c;Service&#xff0c;BroadcastReceiver&#xff0c;ContentProvider。此博客仅仅对安卓中四大组件从整体上进行简单的分析&#xff0c;了解他们在安卓系统框架中处的位置与作用&…

java ee 指南 pdf_Java EE 7权威指南:卷1(原书第5版) 中文pdf

资源名称&#xff1a;Java EE 7权威指南&#xff1a;卷1(原书第5版) 中文pdf第一部分 引言第1章 概述 2第2章 使用教程示例 27第二部分 平台基础知识第3章 资源创建 38第4章 注入 41第5章 打包 44第三部分 Web层第6章 Web应用入门 50第7章 JSF技术 66第8章 Facelets…

PYTHON招聘需求与技能体系

为什么80%的码农都做不了架构师&#xff1f;>>> 目前国内的招聘Python&#xff0c;基本都是偏向web后台开发&#xff0c;偶有高大上的数据挖掘&机器学习 这是之前(2012年)找工作整理的一些JD&#xff0c;在梳理几年来的笔记&#xff0c;顺带理一理 可以以此建…

《FPGA全程进阶---实战演练》第二十一章 电源常用类型:LDO和 DCDC

高速电路中的电源设计 高速电路中的电源设计大概分为两种&#xff0c;一种是集总式架构&#xff0c;一种是分布式架构。集总式架构就是由一个电源输入&#xff0c;然后生成多种所需要的电压。如图1所示。这种架构会增加多个DC/DC模块&#xff0c;这样成本不可控&#xff0c;PCB…

迁云架构实践

本文着笔介绍IT互联网化为传统企业带来的技术挑战&#xff0c;并对上云架构最佳实践进行了深入介绍&#xff0c;首发于阿里云&《程序员》联合出品的《凌云》杂志。 作者&#xff1a; 王宇德&#xff0c;张文生 云计算作为信息技术领域的一种创新应用模式&#xff0c;自其诞…