番外-Python相关语法汇总

机器学习

Posted by RavenZhao on March 16, 2020

在线性回归问题的算法实现过程中,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.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])
  1. 数据插入

在处理回归模型的假设函数的以后,通常会将第一个参数$\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
  1. 数据运算

    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)