这是个课程报告
#更新项目1手写体识别的README
#221 sklearn改进
-
将数据集更换为train2.csv(数据集容量提升到了8000), 使用相同脚本预测性别时准确率从70%提升到了80%, 说明在2000以上的样本容量对准确率依然有影响
-
数据预处理, 使用平均值填充缺失值
1
2
3
4
5
6
7
|
def load_data():
# 数据集已合并, 去掉了标签行, sex预处理为数字
df = pd.DataFrame(pd.read_csv('train2.csv', names=class_names_train2))
# 转化为字符串
df = df.convert_objects(convert_numeric=True)
# 使用平均值填充缺失值
df = df.fillna(df.mean())
|
- 按照年龄将数据集分为不同年龄段分段进行训练
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
def split_data(df, low, high):
"""
:param df: 输入的dataframe
:param low: 截取区间的低阈值
:param high: 截取区间的高阈值(不包含)
:return: 截取的dataframe
"""
df_lowcut = df[df['age'] >= low]
df_cut = df_lowcut[df_lowcut['age'] < high]
selected_names = [x for x in class_names_train2 if (x != 'age' and x != 'sex')]
x_data = df_cut[selected_names].as_matrix()
y_data = df_cut['age'].as_matrix()
# 用平均值填充nan
def fill_nan(np_array):
col_mean = np.nanmean(np_array, axis=0)
nan_ids = np.where(np.isnan(np_array))
np_array[nan_ids] = np.take(col_mean, nan_ids[1])
return np_array
x_data = fill_nan(x_data)
print 'x有没有nan值:', np.any(np.isnan(x_data))
print 'y有没有nan值:', np.any(np.isnan(y_data))
return x_data, y_data
|
-
区分年龄段之后, 预测结果如下
-
0-25岁
训练集准确率为: 0.983532934132
测试集准确率为: 0.906040268456
-
25-60岁
训练集准确率为: 0.843848874
测试集准确率为: 0.373534338358
-
60-80岁
训练集准确率为: 0.977473065622
测试集准确率为: 0.649122807018
由此可见, 区分不同年龄段之后预测准确率有了极大提升, 尤其是低年龄段, 说明不同年龄段的分布不是一样的.
- 在概率近似正确(PAC)学习框架中, 一个类如果存在:
- 一个多项式复杂度的学习算法,正确率略大于随机猜测(例如二分类问题中大于1/2),称弱可学习的类
- 一个多项式复杂度的学习算法,并且正确率很高,称强可学习的类
- Kearns和Valiant证明了强可学习和弱可学习是等价的
- Adaboost算法就是将弱学习器组成强学习器的算法
-
测试集:
T={(x(1),y(1)),(x(2),y(2)),⋯,(x(N),y(N))},x∈χ⊆RN,y∈{+1,−1}with weights: Di=(wi1,wi2,⋯,wiN)
-
分类器
Gm(x):χ→{+1,−1},G(x)=i=1∑MαmGm(x)with weights: A=(α1,α2,⋯,αM)
输入: 训练集 T, m个弱分类器 Gm(x)
输出: 集成分类器 G(x)
-
初始化权重:
D1=(w11,w12,⋯,w1N),w1i=N1
-
For m=1,2,⋯,M:
(a) 对具有权重分布 Dm=(wm1,wm2,⋯,wmN) 的训练集 T 训练出弱分类器 Gm(x)
(b) 计算弱分类器 Gm(x) 在 T 上的误差率:
em=P(Gm(x(i))=y(i))=i=1∑NwmiI(Gm(x(i))=y(i))(1)
(c) 计算 Gm(x) 的系数 αm:
αm=21lnem1−em(2)
(d) 更新训练集的权重分布 Dm+1:
Dm+1=(wm+1,1,wm+1,2,⋯,wm+1,N)
wm+1,i=⎩⎨⎧Zmwmie−αm,Zmwmieαm,Gm(x(i))=y(i)Gm(x(i))=y(i)=Zmwmie−αmy(i)Gm(x(i))(3)
Zm=i=1∑Nwmie−αmy(i)Gm(x(i))
这里 Zm 是规范化因子, 使得 Dm+1 成为一个概率分布, 保证了 i=1∑Nwm+1,i=1
最后,组合所有弱分类器 Gm(x):
f(x)=i=1∑MαmGm(x),G(x)=sign(f(x))
注意到每次迭代测试集中的样本都具有不同的权重, 实现方法有:
- 在每个弱分类器计算损失函数的时候, 对相应样本的loss乘以权重
- 不需要修改弱分类器的方案: 直接修改训练集
- 每次迭代都使用 Di 作为概率分布从原始数据集中生成新的数据集进行训练

- 对模型来说, 这里显示了弱分类器 Gm(x)的权重, αm=21lnem1−em 与误差 em, 变化的关系. 显然更精确的弱分类器具有更大的权重
- 相反, 对训练集数据来说, 从 (3) 可知被误分类的样本的权重会增加, 被正确分类的样本权重会降低, 增加/降低的速度都是指数级别的.
考虑一个与adaboost相似的累加模型 (additive model) :
f(x)=m=1∑Mβmb(x;γm)
in which b(x;γm) 是基分类器, γm 是基分类器的参数, βm 是基分类器的权重.
为了最小化损失函数, 提出前向分步算法(forward stagewise algorithm), 每一步只学习一个基函数及其系数, 即每一步中把其他所有模型看成常数, 只优化一个模型. 这是前向分步算法的核心概念.
输入: 训练集 T={(x(1),y(1)),(x(2),y(2)),⋯,(x(N),y(N))}, 损失函数L(y,f(x)), 基分类器 b(x;γ)
输出: 累加模型 f(x), 初始化 f0(x)=0
- For m=1,2,⋯,M:
- 最小化损失函数:
(βm,γm)=argβ,γmini=1∑NL(y(i),fm−1(x(i))+βb(x(i);γ))
- 更新 f(x):
fm(x)=fm−1(x)+βmb(x(i);γm)
- 生成累加模型:
f(x)=m=1∑Mβmb(x;γm)
以下证明, adaboost算法实际上就是一个使用累加模型, 且损失函数为指数函数的模型
假设损失函数为指数损失函数(exponential loss function):
L(y,f(x))=exp(−yf(x))
在第m次迭代中:
fm(x)=fm−1(x)+αmGm(x),其中 fm−1(x)=m=1∑M−1αmGm(x)
为了最小化
(αm,Gm)=argα,Gmini=1∑Nexp{−y(i)(fm−1(x(i))+αmGm(x(i)))}=argα,Gmini=1∑Nwmiexp{−y(i)αmGm(x(i))}
其中 wmi=exp(−y(i)fm−1(x(i))), 则损失函数仅依赖于α 和 G.
为了证明算法中的αm∗,Gm∗ 与adaboost中的 αm,Gm 等价:
- 对任意 α>0, 使损失函数最小的 Gm∗(x) 由下式得到:
Gm∗(x)=argGmini=1∑NwmiI(y(i)=G(x(i)))
此分类器即为adaboost算法中的基分类器, 即 Gm∗(x)=Gm(x)
- 求 αm
L(α,Gm)=i=1∑Nwmiexp{−y(i)αGm(x(i))}=(eα−e−α)=∑wmi+e−α∑wmi
其中=∑ 是y(i)=G(x(i))∑的缩写, ∑ 是i=1∑N 的缩写
对α求导并使导数为0, 即得到使损失函数最小的α
∂α∂L(α,Gm)=(eα+e−α)=∑wmi−e−α∑wmi=0
s.t. (eα+e−α)∑wmi=∑wmi−e−α=0
s.t. (eα+e−α)ϵm−e−α=0
s.t. α∗=21lnϵm1−ϵm
其中 ϵm=∑wmi=∑wmi, 即为 Gm∗(x) 的损失函数
最后我们得到 (αm,Gm) 是和adaboost中的一致的
Horse Colic Data Set
- 马是否得了疝气的预测
- 数据集大小: 68
- 特征数目: 24
- 数据集是否完整: 有缺失数据, 用0补全
- 基分类器使用决策树桩
- 基分类器的数据结构: 采用字典结构
- 基分类器的训练: 损失函数为0-1损失函数, 找到最好阈值
- adaboost算法
- 集成分类器的数据结构: 采用类保存权重list和基分类器list
- 算法训练
- 保存/载入模型的功能: 读取/保存为json文件
- 模型评估: 准确率和ROC曲线
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
import numpy as np
def stump_classifier(data_matrix, feature_index, threshold, rule='lt'):
"""
:param: data_matrix: 测试集, 样本按行排列
:param: feature_index: 用来分类的特征序号
:param: threshold: 阈值
:param: rule: 规则, 默认情况下是小于阈值被分类为-1.0
决策树桩
输入: 测试集
输出: 预测结果(以1,-1标注)
"""
results = np.ones((data_matrix.shape[0], 1))
feature_values = data_matrix[:, feature_index]
if rule == 'lt':
results[np.where(feature_values <= threshold)[0]] = -1.0
elif rule == 'gt':
results[np.where(feature_values > threshold)[0]] = -1.0
else:
print('ERROR: rule not recognized, use default as lt.')
results[np.where(feature_values <= threshold)[0]] = -1.0
return results
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
import numpy as np
def create_stump(data_matrix, labels, data_weights, step_number=10):
"""
:param: data_matrix: 测试集, 样本按行排列
:param: labels: 标注
:param: data_weights: 训练集样本权重
:param: step_number: 迭代次数, 亦即设置阈值每一步的步长
:return: stump: 决策树桩, 用dict实现
:return: return_prediction: 预测的标注值
:return: min_error: 最小损失函数值
决策树桩训练函数
输入: 训练集, 训练集权重, 迭代次数
输出: 决策树桩, 输出值, 最小损失
"""
m, n = np.shape(data_matrix)
stump = {}
return_prediction = np.zeros((m, 1))
min_error = np.inf
# 遍历特征
for i in range(n):
min_value = data_matrix[:, i].min()
max_value = data_matrix[:, i].max()
step_size = (max_value - min_value) / float(step_number)
# 按步长设定阈值
for j in range(-1, step_number + 1):
for rule in ['lt', 'gt']:
threshold = min_value + j * step_size
prediction = stump_classifier(data_matrix, i, threshold, rule)
# is_error用来存放是否错误的标记, 即I(prediction = labels)
is_error = np.ones((m, 1))
is_error[prediction == labels] = 0
# 损失乘以归一化的权重
weighted_error = np.dot(data_weights.T, is_error)
if weighted_error < min_error:
min_error = weighted_error
return_prediction = prediction.copy()
stump['feature_index'] = i
stump['threshold'] = threshold
stump['rule'] = rule
return stump, return_prediction, min_error
|
1
2
3
4
5
6
7
8
9
|
import numpy as np
class Model:
"""
:return: model_weights: np数组,弱分类器权重
:return: model_list: weak model list, 其中每个弱分类器用dict实现
"""
def __init__(self, size=10):
self.model_weights = np.zeros((size, 1))
self.model_list = []
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
import numpy as np
def adaboost_train(data_matrix, labels, iteration=40):
"""
:param: data_matrix: (m,n) np数组, 训练集, 样本按行排列
:param: labels: (m,1) np数组 标注
:param: iteration: int 弱分类器个数
输入训练集和弱分类器个数, 输出模型
"""
number = data_matrix.shape[0]
data_weights = np.ones((number, 1)) / number # 初始化训练集权重为1/number
m = Model(iteration) # 初始化模型权重为0
for i in range(iteration):
stump, predictions, weighted_error = st.create_stump(data_matrix, labels, data_weights)
m.model_list.append(stump)
m.model_weights[i] = 0.5 * math.log((1.0 - weighted_error) / max(weighted_error, 1e-16))
data_weights = data_weights * np.exp(-1.0 * m.model_weights[i] * labels * predictions)
data_weights = data_weights / np.sum(data_weights)
return m
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
import numpy as np
def adaboost_classify(input_matrix, m):
"""
:param: data_matrix: (m,n) np数组,测试集, 样本按行排列
:param: m: 模型
:return: models_output: (m,1) np数组,强分类器输出值
ensemble model, 输入训练集, 返回输出结果
"""
models_output = np.zeros((input_matrix.shape[0], 1))
for i in range(len(m.model_list)):
model_prediction = st.stump_classifier(input_matrix,
m.model_list[i]['feature_index'],
m.model_list[i]['threshold'],
m.model_list[i]['rule'])
models_output += m.model_weights[i] * model_prediction
return models_output
|
1
2
3
4
5
6
7
8
9
10
11
12
|
import numpy as np
def adaboost_test(data_matrix, labels, m):
"""
:param: data_matrix: 测试集, 样本按行排列
:param: labels: 标注
输入测试集和模型, 输出模型参数, 输出结果和正确率, 返回输出结果
"""
models_output = adaboost_classify(data_matrix, m)
i_vec = (np.sign(models_output) == labels).astype(int)
error_rate = 1 - np.count_nonzero(i_vec) / float(data_matrix.shape[0])
print 'model weights:'+'\n', m.model_weights, '\n'+'models_output:'+'\n', models_output, '\n'+'error rate: ', error_rate
return models_output
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
import json
def save_model(m):
weights_json = json.dumps(m.model_weights.tolist(), indent=4, separators=(',', ': '))
models_json = json.dumps(m.model_list, indent=4, separators=(',', ': '))
with open("model/model_weights.json", 'w') as fp:
fp.write(weights_json)
with open("model/model_list.json", 'w') as fp:
fp.write(models_json)
def load_model():
with open("model/model_weights.json") as fp:
weights_json = json.loads(fp.read())
weights_list = np.array(weights_json)
size = weights_list.shape[0]
model = ab.Model(size)
model.model_weights = weights_list
with open("model/model_list.json") as fp:
model_list = json.loads(fp.read())
model.model_list = model_list
return model
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
import numpy as np
import adaboost as ab # adaboost训练脚本
import test_toolkit as tt # 测试用工具包, 主要用于数据预处理(未展示)
import plot_roc as plot # 绘制ROC曲线, 评估模型优劣(未展示)
# 载入数据
data_matrix = tt.load_data('test_data/horseColicTest2.txt')
# 预处理, 测试集/训练集分为8:2
train_matrix, cv_matrix, test_matrix = tt.split_data(data_matrix, (0.8, 0.0))
train_matrix_x, class_vector = tt.separate_x_y(train_matrix)
1. 自己训练-
m = ab.adaboost_train(train_matrix_x, class_vector)
--2. 载入预训练-
# m = load_model()
-测试模型泛化
test_matrix_x, test_labels = tt.separate_x_y(test_matrix)
results = ab.adaboost_test(test_matrix_x, test_labels, m)
# 绘制ROC曲线
plot.plot_roc(results, test_labels)
# 保存模型
save_model(m)
|
1
2
3
4
5
|
models_output:
[[ 1. -1. 1. 1. -1. -1. 1. 1. 1. 1. 1. 1. 1. 1.]]
labels:
[[ 1. -1. 1. 1. -1. -1. 1. 1. 1. 1. 1. 1. 1. 1.]]
error rate: 0.0
|

AUC曲线完美, 准确率也是100%.在如此数据缺失, 且特征多的情况下还能达到这么高的效果, 显示了adaboost的强大
Caffe,全称Convolution Architecture For Feature Extractioncaffe是一个清晰,可读性高,快速的深度学习框架。
- 使用MNIST手写字体数据集训练Lenet
1
2
3
|
cd $CAFFE_ROOT
./data/mnist/get_mnist.sh
./examples/mnist/create_mnist.sh
|
该脚本会下载好训练集和测试集并在./examples/mnist/
下生成两个lmdb文件.
caffe生成的神经网络是通过prototxt文件定义的, 其中涉及每一层的参数, caffe可以通过这些参数自动生成对应的层并组合成卷积神经网络. 其中Lenet的prototxt的卷积层定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
layer {
name: "conv1"
type: "Convolution"
param { lr_mult: 1 }
param { lr_mult: 2 }
convolution_param {
num_output: 20
kernel_size: 5
stride: 1
weight_filler {
type: "xavier"
}
bias_filler {
type: "constant"
}
}
bottom: "data"
top: "conv1"
}
|
简单介绍下重要参数含义:
num_output
: 输出层维度
kernel_size
: 卷积核大小
stride
: 卷积层步长
其他训练通用参数定义在lenet_solver.prototxt中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
# The train/test net protocol buffer definition
net: "examples/mnist/lenet_train_test.prototxt"
test_iter: 100
# Carry out testing every 500 training iterations.
test_interval: 500
# The base learning rate, momentum and the weight decay of the network.
base_lr: 0.01
momentum: 0.9
weight_decay: 0.0005
# 学习率
lr_policy: "inv"
gamma: 0.0001
power: 0.75
# Display every 100 iterations
display: 100
# 最大迭代次数
max_iter: 5000
# 5000次迭代之后保存模型快照
snapshot: 5000
snapshot_prefix: "examples/mnist/lenet"
# 使用CPU模式
solver_mode: CPU
|
运行完毕之后显示
1
2
3
4
5
6
|
I0126 17:32:32.171516 18290 solver.cpp:246] Iteration 10000, loss = 0.00453533
I0126 17:32:32.171550 18290 solver.cpp:264] Iteration 10000, Testing net (#0)
I0126 17:32:40.498195 18290 solver.cpp:315] Test net output #0: accuracy = 0.9903
I0126 17:32:40.498236 18290 solver.cpp:315] Test net output #1: loss = 0.0309918 (* 1 = 0.0309918 loss)
I0126 17:32:40.498245 18290 solver.cpp:251] Optimization Done.
I0126 17:32:40.498249 18290 caffe.cpp:121] Optimization Done.
|
说明准确率达到了99.03%, 损失函数下降到了0.00453533.
- 使用Faster R-CNN进行物体识别
使用了在VOC2006数据集上预训练的模型, 运行demo如下:

完成一篇署名博客,博客内容至少包括课程学习心得总结、通过commit/pr等说明自己的功劳和苦劳、提供自己的版本库URL并附上安装运行方法和Demo过程截图、其他重要事项等。
项目版本库
为了将sklearn集成到BloodTestReportOCR中, 还将训练好的模型保存为age.pkl
和gender.pkl
并在view.py
中调用
- 保存模型
1
2
3
4
5
|
# 保存预训练模型
from sklearn.externals import joblib
joblib.dump(clf, 'gender.pkl')
# 其他代码
joblib.dump(clf, 'age.pkl')
|
- 调用保存的预训练模型预测
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
# 在view.py中调用预训练的模型
from sklearn.externals import joblib
import numpy as np
# 载入模型
age_clf = joblib.load('age.pkl')
gender_clf = joblib.load('gender.pkl')
# 预测函数
def predict(arr):
# 根据预训练的模型分别从原始数据中选取不同的特征
age_vector = [arr[0][i] for i in [2, 0, 14, 9, 18, 17]]
gender_vector = [arr[0][i] for i in [13, 11, 17]]
# 按照选取的特征预测
gender = gender_clf.predict(gender_vector)
age = age_clf.predict(age_vector)
return gender[0], age[0]
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
# 安装numpy,
sudo apt-get install python-numpy # http://www.numpy.org/
# 安装opencv
sudo apt-get install python-opencv # http://opencv.org/
##安装OCR和预处理相关依赖
sudo apt-get install tesseract-ocr
sudo pip install pytesseract
sudo apt-get install python-tk
sudo pip install pillow
# 安装Flask框架、mongo
sudo pip install Flask
sudo apt-get install mongodb # 如果找不到可以先sudo apt-get update
sudo service mongodb started
sudo pip install pymongo
# 用sklearn进行预测需要的框架
sudo apt-get install cython python-scipy
pip install -U scikit-learn
pip install pandas
|
1
2
|
cd BloodTestReportOCR
python view.py # upload图像,在浏览器打开http://yourip:8080
|
提交图片:

生成报告:

预测性别年龄:

本次课程学习了以下知识
- 课程项目A1a:神经网络实现手写字符识别系统
- 学习了基本前后端输入图片进行检测的原理
- 学习了具有一个隐含层的最简单神经网络的构造: 前向传播, 反向传播的原理
- 课程项目A2:血常规检验报告的图像OCR识别
- 学习了使用github合作项目的流程: 提交pull request, 从源版本库fetch最新版本, merge
不同分支
- 课程项目A3:根据血常规检验的各项数据预测年龄和性别
- 学习了使用sklearn进行预测
- 学习了最基本的数据清洗: 归一化, 标准化, 缺失值填充
- 学习了pandas数据处理包的使用
- 学习了numpy的使用
- Presentation
- 学习了如何使用python编写adaboost, 如何评估模型
- 学习了如何调用caffe, 订制神经网络
本课程学习到了很多知识,对我自己而言, 以python和机器学习的理论知识为主. 孟宁老师也是一个很有风度的老师, 在课堂上和同学们交流, 观点鲜明, 善于抓住每个同学分享知识的重点, 理清楚来龙去脉之后再与其他同学分享, 效果拔群. 感谢孟宁老师给我们带来有如此丰富内容的课程.