在线性回归问题的算法实现过程中,Python的语法规则与Octave(Matlab)存在不一样的地方,简单回顾总结一下。
数据结构
利用Python进行数据操作,离不开几个基础的工具包,如pandas、numpy 在进行数据操作之前,首先需要导入这些包
# 以pd和np作为两个包的简称
import pandas as pd
import numpy as np
pandas提供了多种可以存储数据的格式,如
Series
Series是带有名称和索引的一维数组。在Series中包含的数据类型可以是整数、浮点、字符串、Python对象等。
简单来说,一个Series包括了data、index以及name要素。
# 存储4个价格:
game_price = pd.Series(data=[343.24, 398.59, 343.24, 386.17])
# 通过Series的index对数据进行索引。构建一个与data长度相同的数组。
game_price.index = ["超级玛利欧:奥德赛", "喷射战士2", "玛利欧赛车:豪华版", "塞尔达传说:荒野之息"]
# Series对index可以被命名
game_price.index.name = "游戏名"
# Series本身也可以被命名
game_price.name="游戏价格清单"
# 上面的代码可以用更简练的形式实现:
# 构建索引
name = pd.Index(["超级玛利欧:奥德赛", "喷射战士2", "玛利欧赛车:豪华版", "塞尔达传说:荒野之息"],name = '游戏名')
# 构建Series
game_price = pd.Series(data=[343.24, 398.59, 343.24, 386.17],index = name,name = '游戏价格清单')
在构造Series的时候,不需要设定每个元素的数据类型,Pandas会自动判断一个数据类型,并作为 Series 的类型,同时也可以手动指定数据类型。
# 指定Series的数据类型为浮点型
game_price = pd.Series(data=[343.24, 398.59, 343.24, 386.17],index = name,name = '游戏价格清单',dtype = float)
Series包含了dict的特点,也就意味着可以使用与dict类似的一些操作,Series的index中的元素可以看成是dict中的key。
#获取塞尔达的价格
game_price['塞尔达传说:荒野之息']
#也可利用get方法获取,通过这种方式的好处是当索引不存在时,不会抛出异常。
game_price.get('塞尔达传说:荒野之息')
Series除了像dict外,也非常像ndarray,可以采用切片操作。
# Series的索引默认从0开始
# 获取第一个元素
game_price[0]
# 获取前三个元素
game_price[:3]
# 获取价格大于350的元素
game_price[game_price > 350]
# 获取第四个和第二个元素
game_price[[3,1]]
尽管形式类似,但Series仍然不是一维数组,用shape方法查看Series的维度时,只会返回$(n,)$而不像一维数组那样,返回$(n,1)$。所以,不要轻易用一维数组与序列相乘,可能会事与愿违——np.dot()与‘*’的区别。
Dataframe
数据框是是一个带有索引的二维数据结构,每列可以有自己的名字,并且可以有不同的数据类型,可以把它想象成一个excel表格或者数据库中的一张表。
构建数据框的方法
# 构建数据框的索引
index = pd.Index(data=["超级玛利欧:奥德赛", "喷射战士2", "玛利欧赛车:豪华版", "塞尔达传说:荒野之息"],name = '游戏名')
# 以字典的形式构建数据框的数据。key代表信息种类,value代表数据
game = {
"游戏价格": [343.24, 398.59, 343.24, 386.17],
"游戏类型": ["动作", "射击", "运动", "角色扮演"]
}
# 利用索引(index)和数据(data)构建数据框
game_info = pd.DataFrame(data=game, index=index)
在上面构建的game_info这个数据框中,索引(行)是游戏名,包含了两列数据:游戏价格和游戏类型。 除了上面这种传入dict的方式构建外,我们还可以通过另外一种方式来构建。这种方式是先构建一个二维数组,然后再生成一个列名称列表。
game = [[343.24,'动作'],
[398.59,'射击'],
[343.24,'运动'],
[386.17,'角色扮演']]
game_index = pd.Index(data=["超级玛利欧:奥德赛", "喷射战士2", "玛利欧赛车:豪华版", "塞尔达传说:荒野之息"],name = '游戏名')
columns_names = ['游戏价格','游戏类型']
game_info = pd.DataFrame(data=game,index=game_index,columns=columns_names)
NumPy
除了pandas提供的数据结构之外,numpy作为Python的一个扩充程序库,可以进行高阶大量的维度数组与矩阵运算,并针对数组运算提供了大量的数学函数库。
NumPy是使用Python进行科学计算的基础包。它包含如下的内容:
- 一个强大的N维数组对象。
- 复杂的(广播)功能。
- 用于集成C / C ++和Fortran代码的工具。
- 有用的线性代数,傅里叶变换和随机数功能。
- 除了明显的科学用途外,NumPy还可以用作通用数据的高效多维容器。可以定义任意数据类型。这使NumPy能够无缝快速地与各种数据库集成。
关于NumPy的应用,中文圈有非常详细的教程:NumPy快速入门教程
在这里,只简要总结一下在回归问题求解过程中,涉及到的一些操作。
NumPy的主要对象是同构多维数组。它是一个元素表(通常是数字),所有类型都相同,由非负整数元组索引。在NumPy维度中称为轴。例如训练数据集中,每一个样本可能具有多个(假如5个)特征$[1,2,3,4,5]$,这些特征组成的特征向量具有一个轴,它的长度为5.如果训练集中有两个样本的时候,两个样本构成了特征矩阵[[1,2,3,4,5],[3,2,1,2,4]]:
$\begin{bmatrix} 1&2&3&4&5 \ 3&2&1&2&4\end{bmatrix}$
这个数组有两个轴,第一个轴为行数,长度为2,第二个轴为列数,长度为5.
NumPy的数组类调用ndarray。它也被别名所知array。请注意,numpy.array与标准Python库类的array.array不同,后者只处理一维数组并提供较少的功能。ndarray对象更重要的属性是:
- ndarray.ndim - 数组的轴(维度)的个数。在Python世界中,维度的数量被称为rank。
- ndarray.shape - 数组的维度。这是一个整数的元组,表示每个维度中数组的大小。对于有 n 行和 m 列的矩阵,shape 将是 (n,m)。因此,shape 元组的长度就是rank或维度的个数 ndim。
- ndarray.size - 数组元素的总数。这等于 shape 的元素的乘积$(n\times m)$。
- ndarray.dtype - 一个描述数组中元素类型的对象。可以使用标准的Python类型创建或指定dtype。另外NumPy提供它自己的类型。例如numpy.int32、numpy.int16和numpy.float64。
- ndarray.itemsize - 数组中每个元素的字节大小。例如,元素为 float64 类型的数组的 itemsize 为8(=64/8),而 complex32 类型的数组的 itemsize 为4(=32/8)。它等于 ndarray.dtype.itemsize 。
- ndarray.data - 该缓冲区包含数组的实际元素。通常,我们不需要使用此属性,因为我们将使用索引访问数组中的元素。
为了满足不同的需求,我们可以利用不同的方法生成数组。
- 利用array函数,从Python列表或元组中创建数组
# [(0,1,2,3,4),(5,6,7,8,9)]是一个Python列表
X = np.array([(0,1,2,3,4),(5,6,7,8,9)])
X
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9]])
- 在大多数情况下,一个数组的形状是确定,但是其数据是未知的,我们可以利用以下的一些方法创建具有特定形状的数组:
# 函数zeros创建一个由0组成的数组
X = np.zeros((3,4)) #数组维度3*4
X
array([[ 0., 0., 0., 0.],
[ 0., 0., 0., 0.],
[ 0., 0., 0., 0.]])
# 函数ones创建一个由1组成的数组
X = np.ones((2, 3,4)) #数组维度2*(3*4)
X
array([[[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]],
[[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]]])
# 函数empty创建一个内容随机的数组,初始内容取决于内存状态。
X = np.empty((2,3))
# 利用arange函数,按照一定规律填充。
X = np.arange(15)
X
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14])
X = np.arange(1,15,3)
X
array([ 1, 4, 7, 10, 13])
# 在上面的案例中,当arange与浮点参数一起使用时,由于有限的浮点精度,通常不可能预测所获得的元素的数量。
#出于这个原因,通常最好使用linspace函数来接收我们想要的元素数量的函数,而不是步长(step):
X = np.linspace( 0, 2, 9 ) #生成一个从0到2的序列,共包含9个元素。
X
array([0. , 0.25, 0.5 , 0.75, 1. , 1.25, 1.5 , 1.75, 2. ])
# 利用reshape函数,可以将arange和linespace生成的数组,变换为需要的形状。
X = np.arange(15).reshape(5,3)
X
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]])
X = np.linspace(0,2,9)
X
array([[0. , 0.25, 0.5 ],
[0.75, 1. , 1.25],
[1.5 , 1.75, 2. ]])
# 利用random函数生成随机矩阵
b = np.random.random((2,3))
b
array([[0.49243765, 0.01915367, 0.69298595],
[0.4000049 , 0.26478978, 0.21933073]])
# 生成指定形状的整数矩阵
# 0,10表示整数的范围,(2,3)表示数组的形状
b = np.random.randint(0,10,(2,3))
b
array([[4, 8, 6],
[6, 6, 5]])
数据操作
-
数据读取
1.1 数据框的操作
在进行模型训练之前,首先需要读取训练集数据,通常数据都会以文件的形式进行存储,如’.txt’,‘.csv’文件等。
利用pandas中的readcsv方法可以将文件中的数据读取到程序中,并以数据框的形式进行存储。
需要注意的是,默认情况下,读取数据的时候,会将第一行作为表头,所以如果原始数据中不包括表头信息,那么在读取数据的时候需要设置参数header=None。注意Python是大小写敏感的。
data = pd.readcsv('data.txt',header=None)
在完成数据读取之后,我们可以手动添加每列的名称。
data.columns = ['column1','column2','column3']
访问数据框的列,可以通过属性(“.列名”)的方式来访问该列的数据,也可以通过[column1]的形式来访问该列的数据。
data.column1
data['column1']
如果想要获取多个列的数据,可以传入由列名组成的列表,并且可以打乱列的顺序。
data[['column2','column1']]
但是,如果想要连续读取多个列的时候,一个个罗列列名就太麻烦了。
例如在监督学习中,我们所利用的训练集,前n-1列为特征矩阵,最后一列为标签列。想要从数据框中把特征和标签分离出来,需要利用批量读取列的方法。
iloc方法接受列名的索引,使得我们可以对列使用切片的方法对数据进行选取。列的索引默认从0开始,因此第一列的索引号为0
data.iloc[:,0] #读取第一列
data.iloc[0:2,0] #读取第一列的前两行
data.iloc[:,-1] #读取最后一列的数据
从上面的代码还能注意到,用iloc进行切片的时候,第一个参数表示对行索引的切片,第二个参数表示对列索引的切片。并且,切片的时候,右区间为开区间。
所以,要分离特征和标签,我么你需要从第一列开始,读取至倒数第二列。然后读取最后一列,存储为标签。
X = data.iloc[:,0:-1] #从data的第一列开始,一直到倒数第二行为止。
y = data.iloc[:,-1] #提取data的最后一列。
因为在后续的计算中,涉及到矩阵与向量的各种运算,因此需要把数据框转换为数组的形式。.values方法能够以数组形式返回数据框指定列的所有数据。
X.iloc[:,0] #返回第一列数据所对应的Series
X.iloc[:,0].values #返回第一列数据所对应的array。
1.2 数组的操作
一维数组可以直接通过索引进行读取。
a = np.arange(10)**3
a
array([ 0, 1, 8, 27, 64, 125, 216, 343, 512, 729])
a[2] # 直接通过索引读取数据
8
a[2:5] # 利用区间索引数据,左闭右开
array([ 8, 27, 64])
a[:6:2] = -1000 # 相当于0:6:2,即从第1个元素,到第5个元素,每两个元素将其赋值为-1000
>>> a
array([-1000, 1, -1000, 27, -1000, 125, 216, 343, 512, 729])
>>> a[ : :-1] #将数组反向(-1)排序
array([ 729, 512, 343, 216, 125, -1000, 27, -1000, 1, -1000])
多维的数组每个轴可以都有一个索引。这些索引以逗号分隔的元组给出。
# 矩阵b
b = np.array([[ 0, 1, 2, 3],
[10, 11, 12, 13],
[20, 21, 22, 23],
[30, 31, 32, 33],
[40, 41, 42, 43]])
b[2,3] #索引默认都是从0开始,[2,3]是第三行,第四列
23
b[0:5, 1] # 选择第二列都所有数据
array([ 1, 11, 21, 31, 41])
b[ : ,1] # 选择第二列都所有数据
array([ 1, 11, 21, 31, 41])
b[1:3, : ] # 选择第二到第三行数据
array([[10, 11, 12, 13],
[20, 21, 22, 23]])
# 当提供的索引少于轴的数量时,缺失的索引默认为完整切片
b[-1] # 选择最后一行,等价于b[-1,:]
array([40, 41, 42, 43])
- 数据插入
在处理回归模型的假设函数的以后,通常会将第一个参数$\theta_0$看作是$\theta_0\times 1$,因此,还要在特征矩阵前插入一列全为1的向量,作为$x_0$。
在数组中插入数据,可以利用numpy包中的insert方法。
如果X是一个数组,利用numpy.insert方法,X表示被插入的对象,0表示插入的位置,values表示插入的数值,axis=1表示按列插入,axis=0表示按行插入。利用该方法插入数据后,X本身并不会改变,因此需要插入后的数组重新赋值给X。
如果X是DataFrame,可以利用数据框的insert方法直接在数据框中插入数据。
X = np.insert(X,0,values=1,axis=1) #X是array
X.insert(0,value=1,column='x0') #X是DataFrame
-
数据运算
3.1 数组array的运算
数组上的算术运算符会应用到元素级别。
>>> a = np.array( [20,30,40,50] ) >>> b = np.arange( 4 ) >>> b array([0, 1, 2, 3]) >>> c = a-b >>> c array([20, 29, 38, 47]) >>> b**2 array([0, 1, 4, 9]) >>> 10*np.sin(a) array([ 9.12945251, -9.88031624, 7.4511316 , -2.62374854]) >>> a<35 array([ True, True, False, False])
乘积运算符*在NumPy数组中按元素进行运算。矩阵乘积可以使用@运算符或dot函数进行。
>>> A = np.array( [[1,1],[0,1]] ) >>> B = np.array( [[2,0],[3,4]] ) >>> A * B # 按照元素相乘 array([[2, 0], [0, 4]]) >>> A @ B # 矩阵乘法 array([[5, 4], [3, 4]]) >>> A.dot(B) # 矩阵乘法另一种形式 array([[5, 4], [3, 4]])
某些操作(例如+=和 *=)会更直接更改被操作的矩阵数组而不会创建新的矩阵数组。
>>> a = np.ones((2,3), dtype=int) >>> b = np.random.random((2,3)) >>> a *= 3 >>> a array([[3, 3, 3], [3, 3, 3]]) >>> b += a >>> b array([[ 3.417022 , 3.72032449, 3.00011437], [ 3.30233257, 3.14675589, 3.09233859]]) >>> a += b # b is not automatically converted to integer type Traceback (most recent call last): ... TypeError: Cannot cast ufunc add output from dtype('float64') to dtype('int64') with casting rule 'same_kind'
数组的求和。
默认情况下,.sum()函数会计算数组中所有元素的和,可以通过指定轴来按列(axis=0)或者按行(axis=1)求和,
>>> b = np.arange(12).reshape(3,4) >>> b array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]]) >>> b.sum(axis=0) # 按列求和 array([12, 15, 18, 21]) >>> b.min(axis=1) # 按行查找最小值。 array([0, 4, 8]) >>> b.cumsum(axis=1) # 按行进行元素累加。 array([[ 0, 1, 3, 6], [ 4, 9, 15, 22], [ 8, 17, 27, 38]])s
数据绘图
利用Python绘图通常会用到matplotlib包,先导入
import matplotlib as plt
对于单特征变量的训练集,我们可以直接利用特征值和标签绘制数据的散点图。
plt.scatter(X,y)
函数
Python中的函数用def方法声明
def+函数名(参数),以冒号结尾。声明函数的时候,不需要同时声明函数的返回值。一个函数可以返回多个变量,在函数结尾以return的形式输出。
def gradientDescent(X, y, theta, alpha, num_iters):
'''
函数代码
'''
return theta,J_history #函数返回两个变量
调用函数的时候,如果只让函数给一个变量赋值,则默认该变量等于函数输出的第一个变量。
theta, J_history = gradientDescent(X, y, theta, alpha, iterations) #theta和J_history分别获得函数输出的两个变量。
# new_theta获得函数输出的theta值
new_theta = gradientDescent(X,y,theta,alpha,interations)