《吴恩达机器学习》是在2014年发布的(2011年录制),那个时候机器学习的编程语言用octave较多,但是现在几乎都是python的天下,几乎所有的机器学习框架都在python上有很好的实现方式,如TensorFlow,pytorch等。所谓磨刀不误砍柴工,学好python(不求精通)是很有必要的。另外值得提醒的有3点
1、以下的所有实现都是基于python3,若代码不通过请切换到python3。
2、安装python及最基本的使用请参考别的教程,这里只讨论与机器学习有关的python教程
我之前工作大部分使用的是cpp,cpp运行效率卓越但是同时开发效率也非常低。特别是现在开发接口的方式越来越拥抱调用restful接口的形式(可以查考阿里云,百度,科大讯飞的接口),而不是通过提供一种语言的SDK。我也用gunicorn+flask+gevent这种方式写过web接口,测试后发现性能非常好,一点也不比cpp用最底层的epoll这种形式差,现在我基本上能用python开发都不会用cpp。毕竟时间有限,特别是在学习新知识这款,不要纠结语言层的东西,能把算法应用上才是王道,而python非常友好,学习成本也低。
python中有number(包含了整型,浮点型),字符串,这两种使用和别的高级语言没有太多区别,我主要想讨论的是python中比较特殊的数据结构——列表(list)、元组(tuple)和dict(字典)
列表(list)
序列是Python中最基本的数据结构,用中括号 [ ] 表示。序列中的每个元素都分配一个数字 - 它的位置,或索引,第一个索引是0,第二个索引是1,依此类推。以下是一个列表
list1 = ['Google', 'Apple', 1997, 2000]
list2 = [1, 2, 3, 4, 5 ]
list3 = ["a", "b", "c", "d"]
元组(tuple)
Python 的元组与列表类似,不同之处在于元组的元素不能修改。用小括号 ( ) 表示。
tup1 = ('Google', 'Apple', 1997, 2000)
tup2 = (1, 2, 3, 4, 5 )
tup3 = ("a", "b", "c", "d")
字典(dict)
字典是另一种可变容器模型,且可存储任意类型对象。字典的每个键值(key=>value)对用冒号(:)分割,每个对之间用逗号(,)分割,用**花括号 { } ** 表示。
dict1 = {'Name': 'Tom', 'Age': 7, 'Class': 'First'}
dict2 = { 'abc': 123, 98.6: 37 }
print("dict1['Age'] = ", dict1['Age'])
print("dict2[98.6] = ", dict2[98.6])
# 以上代码输出
dict1['Age'] = 7
dict2[98.6] = 37
python用四个空格区分代码结构,不像别的语言使用花括号。所以当你的代码逻辑嵌套太多就会非常乱,也有人调侃这个时候要带一把游标卡尺来量哪一行对应同个代码块,这个我猜是因为设计python的人鼓励你代码别写那么长,才这样设计的(逃~)
if语句
#!/usr/bin/python3
age = int(input("请输入你家狗狗的年龄: "))
print("")
if age < 0:
print("你是在逗我吧!")
elif age == 1:
print("相当于 14 岁的人。")
elif age == 2:
print("相当于 22 岁的人。")
elif age > 2:
human = 22 + (age -2)*5
print("对应人类年龄: ", human)
### 退出提示
input("点击 enter 键退出")
while语句
#!/usr/bin/env python3
n = 100
sum = 0
counter = 1
while counter <= n:
sum = sum + counter
counter += 1
print("1 到 %d 之和为: %d" % (n,sum))
for语句
#!/usr/bin/python3
sites = ["you", "deserve","better","!"]
for site in sites:
print("循环数据 " + site) # print中+表示链接字符串
print("完成循环1")
for num in range(5):
print("循环数 ", num) # print中,可以连接后面的number
print("完成循环2")
切片(Slice)也就是对list、tuple或者dict这种数据结构进行便捷的查询和修改方式,对于机器学习中经常操作训练数据的这种操作,切片是非常重要的,以下来看下传统的数据操作
# 传统中取前3个数据的如下
L = ['lisa', 'tom', 'jack', 'lucy', 'mike']
n = 3
for i in range(n):
print(i)
对这种经常取指定索引范围的操作,用循环十分繁琐,因此,Python提供了切片操作符,能大大简化这种操作.对应上面的问题,取前3个元素,用一行代码就可以完成切片:
L = ['lisa', 'tom', 'jack', 'lucy', 'mike']
print(L[0:3])
L[0:3]表示,从索引0开始取,直到索引3为止,但不包括索引3。即索引0,1,2,正好是3个元素。如果第一个索引是0,还可以省略成L[:3]
以下是几种操作方式
L = ['lisa', 'tom', 'jack', 'lucy', 'mike']
print(L[0:3]) # 表示从索引0到索引3为止,及0, 1, 2
print(L[-2:]) # 表示从倒数第二个数到最后一个数
print(L[::2]) # 表示从所有的数中,每2个数取一个
print(L[:]) # 表示原封不动的同一个L
# 输出如下
>> ['lisa', 'tom', 'jack']
>> ['lucy', 'mike']
>> ['lisa', 'jack', 'mike']
>> ['lisa', 'tom', 'jack', 'lucy', 'mike']
Python 定义函数使用 def 关键字,一般格式如下:
#!/usr/bin/python3
# 计算面积函数
def area(width, height):
return width * height
def print_welcome(name):
print("Welcome", name)
print_welcome("machine learning")
w = 4
h = 5
print("width =", w, " height =", h, " area =", area(w, h))
在Python中创建一个类和对象是很容易的,如下示
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print('{}: {}'.format(self.name, self.score))
a = Student('jack', 98.5)
a.print_sore()
# 输出
>> jack: 98.5
前面是讲的都是python的数据结构和基本使用,而在机器学习下的运算都是使用numpy的(当然不同框架下也不是绝对的)。numpy使用起来也非常的方便,以下着重介绍下矩阵(多维数组)的运算
numpy创建一个矩阵的方式非常方便,如下示
import numpy as np
a = np.array([[1, 2, 3], [4, 5, 6]]) # 创建一个2*3的矩阵
b = np.zeros((3,4)) # 创建3*4全零二维数组
c = np.one((2,3,4)) # 创建2*3*4数值都是1的三维数组
d = np.zeros((5, 1)) # 创建5*1全零二维数组
e = np.identity(5) # 创建一个5*5的单位矩阵
# 输出如下
>> [[1 2 3]
[4 5 6]]
>> [[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]]
>> [[[1. 1. 1. 1.]
[1. 1. 1. 1.]
[1. 1. 1. 1.]]
[[1. 1. 1. 1.]
[1. 1. 1. 1.]
[1. 1. 1. 1.]]]
>> [[0.]
[0.]
[0.]
[0.]
[0.]]
>> [[1. 0. 0. 0. 0.]
[0. 1. 0. 0. 0.]
[0. 0. 1. 0. 0.]
[0. 0. 0. 1. 0.]
[0. 0. 0. 0. 1.]]
矩阵的加减法运算可以直接用 + - 运算符计算,要求矩阵的维度一致,否则会报错
import numpy as np
a = np.array([[1, 2, 3], [4, 5, 6]])
b = np.ones(a.shape)
c = a + b
d = a - b
# 输出
>> a = [[1 2 3]
[4 5 6]]
>> b = [[1. 1. 1.]
[1. 1. 1.]]
>> c = [[2. 3. 4.]
[5. 6. 7.]]
>> d = [[0. 1. 2.]
[3. 4. 5.]]
矩阵的乘法
import numpy as np
A = np.array([[1, 1], [2, 2]])
B = np.array([[2, 0], [3, 4]])
C = np.array([[1, 1, 1], [2, 2, 2]])
D = A * B # 元素点乘
E = np.dot(B, C) # 矩阵乘法
>> D = [[2 0]
[6 8]]
>> E = [[ 2 2 2]
[11 11 11]]
矩阵的转置和逆矩阵,对于方阵A,如果为非奇异方阵,则存在逆矩阵inv(A)。对于奇异矩阵或者非方阵,并不存在逆矩阵,但可以使用pinv(A)求其伪逆
import numpy as np
a = np.array([[3, 3], [2, 2]])
b = np.linalg.pinv(a)
c = np.linalg.inv(a)
d = a.T
>> b = [[0.11538462 0.07692308]
[0.11538462 0.07692308]]
>> c = [[ 6.00479950e+15 -9.00719925e+15]
[-6.00479950e+15 9.00719925e+15]]
>> d = [[3 2]
[3 2]]
上面讲了python和numpy的一些使用,那么我们来实现一个梯度下降法吧,如下示
import numpy as np
import matplotlib.pyplot as plt
# 从20-30 取199个点,并把x_data转为列向量
x_data_re = np.linspace(0, 30, 200)[:, np.newaxis]
x_data = (x_data_re - x_data_re.min()) / x_data_re.max() - x_data_re.min()
# 取概率分布的均值0,标准差为1的正太分布干扰变量
noise = np.random.normal(0, 0.1, x_data.shape)
# 设图像的斜率是2, 截距是6的图像
w = 2; b = 6
y_data =x_data*w + b + noise
# 下面开始用梯度下降法来得到我们的预期值 W 和 B
# 目标方程
def h(x_data, w, b):
return w*x_data + b
# 代价函数
def J(x_data, y_data, w, b):
return np.mean((h(x_data, w, b) - y_data)**2)/2
# 代价函数偏导数w
def dwJ(x_data, y_data, w, b):
return np.mean((h(x_data, w, b) - y_data)*x_data)
# 代价函数偏导数b
def dbJ(x_data, y_data, w, b):
return np.mean(h(x_data, w, b) - y_data)
print('初始损失率是 {}'.format(J(x_data, y_data, w, b)))
# 下面开始进行迭代,lr表示学习率,n_itera表示循环次数,epailon表示当损失函数变化很小时退出的阈值
def gradient_descent(x_data, y_data, w, b, lr=1e-3, n_itera = 1e4, epailon = 1e-8):
w_history = list()
b_history = list()
W = w
B = b
w_history.append(W)
b_history.append(B)
itera = 0
while itera < n_itera:
# 保存上次的值
print('此时的w = {},b = {}, 循环次数 = {}, 损失值 = {}'.format(W, B, itera, J(x_data, y_data, W, B)))
lastW = W
lastB = B
# 计算梯度
gradient_w = dwJ(x_data, y_data, W, B)
gradient_b = dbJ(x_data, y_data, W, B)
# 梯度下降迭代
W = W - lr*gradient_w
B = B - lr*gradient_b
# 保存梯度下降的值
w_history.append(W)
b_history.append(B)
if abs(J(x_data, y_data, W, B) - J(x_data, y_data, lastW, lastB)) <= epailon:
print('此时的w = {},b = {}, 循环次数 = {}, 损失值 = {}'.format(W, B, itera, J(x_data, y_data, W, B)))
break
itera = itera+1
return w_history, b_history
# 开始计算,给定初始值W,B
W = 0
B = 0
W_history, B_history = gradient_descent(x_data, y_data, W, B, lr=0.1)
# 画出训练集的点和我们希望的图像
plt.plot(x_data, y_data, 'bo')
plt.plot(x_data, x_data*W_history[-1] + B_history[-1], 'r-', lw=3)
plt.show()
由于《吴恩达机器学习》系列视频 Octave/Matlab 教程 中的octave已经不适合当前的形势了,故转为python/numpy教程,以便后续学习和查阅。