李宏毅机器学习 P12 HW2 Winner or Loser 笔记(不使用框架实现使用MBGD优化方法和z_score标准化的logistic regression模型)

版权声明:站在巨人的肩膀上学习。 https://blog.csdn.net/zgcr654321/article/details/83820754

建立logistic回归模型:

根据ADULT数据集中一个人的age,workclass,fnlwgt,education,education_num,marital_status,occupation等信息预测其income大于50K或者相反(收入)。

数据集:

ADULT数据集。

train.csv:https://ntumlta.github.io/2017fall-ml-hw2/raw_data/train.csv

test.csv:https://ntumlta.github.io/2017fall-ml-hw2/raw_data/test.csv

test数据集的真实标签:https://ntumlta.github.io/2017fall-ml-hw2/correct_answer.csv

数据集官网:https://archive.ics.uci.edu/ml/datasets/Adult

train中的标签为独热编码,label = 0表示小于等于50K,label = 1表示大于50K。

对于测试数据,计算其预测的准确率。

先利用网格搜索找出一组比较合适的w和b的初值:

我们先利用np生成一系列等分的数组,作为1000组w的值和1000组b的值。

然后我们建立一个1000X1000的矩阵来存储这1000X1000种可能的函数组合的cross_entropy的值。

样本量大小我们取320,本数据集样本总共为32561组,我们大概取了其中1%的数据。注意取样本时我们采用了随机数,取样本时是随机的。

扫描二维码关注公众号,回复: 4023544 查看本文章

代码如下:

from math import exp, log, pow, sqrt
import numpy as np
import csv


# 定义sigmoid函数对output值作处理
def sigmoid(out):
	out = 1.0 / (1 + exp(-out))
	return out


# 有限责任公司(Self-emp-inc),无限责任公司(Self-emp-not-inc),个人(Private),联邦政府(Federal-gov),州政府(State-gov),
#  地方政府( Local-gov),无工作经验人员(Never-worked),无薪人员(Without-pay)
work_class = {
	'Self-emp-inc': 1.0, 'Self-emp-not-inc': 2.0, 'Private': 3.0, 'Federal-gov': 4.0, 'State-gov': 5.0,
	'Local-gov': 6.0,
	'Never-worked': 7.0, 'Without-pay': 8.0, '?': 0.0}
# 教育情况:Bachelors, Some-college, 11th, HS-grad, Prof-school, Assoc-acdm, Assoc-voc, 9th, 7th-8th, 12th, Masters,
# 1st-4th, 10th, Doctorate, 5th-6th, Preschool 
education = {'Bachelors': 1.0, 'Some-college': 2.0, '11th': 3.0, 'HS-grad': 4.0, 'Prof-school': 5.0, 'Assoc-acdm': 6.0,
			 'Assoc-voc': 7.0, '9th': 8.0, '7th-8th': 9.0, '12th': 10.0,
			 'Masters': 11.0, '1st-4th': 12.0, '10th': 13.0, 'Doctorate': 14.0, '5th-6th': 15.0, 'Preschool': 16.0}
# 已婚(Married-civ-spouse),再婚(Married-AF-spouse),已婚配偶缺席(Married-spouse-absent),离婚(Divorced)
# 离异(Separated),丧偶(Widowed),未婚(Never-married)
marital_status = {'Married-civ-spouse': 1.0, 'Married-AF-spouse': 2.0, 'Married-spouse-absent': 3.0, 'Divorced': 4.0,
				  'Separated': 5.0, 'Widowed': 6.0, 'Never-married': 7.0}
# 职业(Occupation):清洁工(Handlers-cleaners),维修工艺(Craft-repair),服务行业(Other-service), 销售(Sales),机床操控人员(Machine-op-inspct),
# 执行管理(Exec-managerial), 专业教授(Prof-specialty),技术支持(Tech-support),行政文员(Adm-clerical),
# 养殖渔业(Farming-fishing), 运输行业(Transport-moving),私人房屋服务(Priv-house-serv),
# 保卫工作(Protective-serv), 武装部队(Armed-Forces)
occupation = {'Handlers-cleaners': 1.0, 'Craft-repair': 2.0, 'Other-service': 3.0, 'Sales': 4.0,
			  'Machine-op-inspct': 5.0,
			  'Exec-managerial': 6.0, 'Prof-specialty': 7.0, 'Tech-support': 8.0, 'Adm-clerical': 9.0,
			  'Farming-fishing': 10.0,
			  'Transport-moving': 11.0, 'Priv-house-serv': 12.0, 'Protective-serv': 13.0, 'Armed-Forces': 14.0,
			  '?': 0.0}
# 妻子(Wife),子女(Own-child),丈夫(Husband),外来人员(Not-in-family)、 其他亲戚(Other-relative)、 未婚(Unmarried)
relationship = {'Wife': 1.0, 'Husband': 2.0, 'Own-child': 3.0, 'Unmarried': 4.0, 'Other-relative': 5.0,
				'Not-in-family': 6.0}
# 白人(White),亚洲太平洋岛民(Asian-Pac-Islander),阿米尔-印度-爱斯基摩人(Amer-Indian-Eskimo)、 其他(Other),黑人(Black)
race = {'White': 1.0, 'Black': 2.0, 'Asian-Pac-Islander': 3.0, 'Amer-Indian-Eskimo': 4.0, 'Other': 5.0}
sex = {'Female': 1.0, 'Male': 2.0}
# 美国(United-States)、 柬埔寨(Cambodia)、 英国(England),波多黎各(Puerto-Rico),加拿大(Canada),德国(Germany)
# 美国周边地区(关岛-美属维尔京群岛等)(Outlying-US(Guam-USVI-etc)),印度(India)、 日本(Japan)、 希腊(Greece)
# 美国南部(South)、 中国(China)、 古巴(Cuba)、 伊朗(Iran)、 洪都拉斯(Honduras),菲律宾(Philippines)
# 意大利(Italy)、 波兰(Poland)、 牙买加(Jamaica)、 越南(Vietnam)、 墨西哥(Mexico)、 葡萄牙(Portugal)
# 爱尔兰(Ireland)、 法国(France)、多米尼加共和国(Dominican-Republic)、 老挝(Laos)、 厄瓜多尔(Ecuador)
# 台湾(Taiwan)、 海地(Haiti)、 哥伦比亚(Columbia)、 匈牙利(Hungary)、 危地马拉(Guatemala)、 尼加拉瓜(Nicaragua)
# 苏格兰(Scotland)、 泰国(Thailand)、 南斯拉夫(Yugoslavia),萨尔瓦多(El-Salvador)、 特立尼达和多巴哥(Trinadad&Tobago)
# 秘鲁(Peru),香港(Hong),荷兰(Holland-Netherlands)

Nation_country = {'United-States': 1.0, 'Cambodia': 2.0, 'England': 3.0, 'Puerto-Rico': 4.0, 'Canada': 5.0,
				  'Germany': 6.0, 'Outlying-US(Guam-USVI-etc)': 7.0, 'India': 8.0, 'Japan': 9.0, 'Greece': 10.0,
				  'South': 11.0, 'China': 12.0, 'Cuba': 13.0, 'Iran': 14.0, 'Honduras': 15.0, 'Philippines': 16.0,
				  'Italy': 17.0, 'Poland': 18.0, 'Jamaica': 19.0, 'Vietnam': 20.0, 'Mexico': 21.0, 'Portugal': 22.0,
				  'Ireland': 23.0, 'France': 24.0, 'Dominican-Republic': 25.0, 'Laos': 26.0, 'Ecuador': 27.0,
				  'Taiwan': 28.0, 'Haiti': 29.0, 'Columbia': 30.0, 'Hungary': 31.0, 'Guatemala': 32.0,
				  'Nicaragua': 33.0, 'Scotland': 34.0, 'Thailand': 35.0, 'Yugoslavia': 36.0, 'El-Salvador': 37.0,
				  'Trinadad&Tobago': 38.0, 'Peru': 39.0, 'Hong': 40.0, 'Holand-Netherlands': 41.0, '?': 0.0}
income = {'>50K': 1.0, '<=50K': 0.0}

train_x_data_set = []
train_y_data_set = []

# 从原始数据中提取出用于train的x数据集和y数据集
with open('train.csv', 'r', encoding='UTF-8', errors='ignore') as csv_file:
	all_lines = csv.reader(csv_file)
	# 遍历train.csv的所有行
	for one_line in all_lines:
		if one_line[0] == 'age':
			continue
		one_line_x_data = []
		one_line_y_data = 0.0
		for i, element in enumerate(one_line):
			# 去除字符串首尾的空格
			element = element.strip()
			if i == 1:
				one_line_x_data.append(work_class[element])
			elif i == 3:
				one_line_x_data.append(education[element])
			elif i == 5:
				one_line_x_data.append(marital_status[element])
			elif i == 6:
				one_line_x_data.append(occupation[element])
			elif i == 7:
				one_line_x_data.append(relationship[element])
			elif i == 8:
				one_line_x_data.append(race[element])
			elif i == 9:
				one_line_x_data.append(sex[element])
			elif i == 13:
				one_line_x_data.append(Nation_country[element])
			elif i == 14:
				one_line_y_data = income[element]
			else:
				one_line_x_data.append(int(element))
		train_x_data_set.append(one_line_x_data)
		train_y_data_set.append(one_line_y_data)


# 测试,一共32561组样本
# print(len(train_x_data_set))
# print(len(train_y_data_set))

# 测试
# for i in range(len(train_x_data_set)):
# 	print(train_x_data_set[i])
# 	print(train_y_data_set[i])


# 对数据作z_score标准化处理,注意,我对所有项的数据都进行了z_score标准化,原因是有几项数据的数量级太大,直接计算时sigmoid函数计算会溢出
def data_z_score_standardization():
	global train_x_data_set
	# 对train_x_data_set中所有的数据都进行归一化
	for index in range(len(train_x_data_set[0])):
		# 取出需要归一化的一组数据
		data_list = []
		for one_data in train_x_data_set:
			data_list.append(one_data[index])
		# 计算这组数据的平均值
		data_mean = 0.0
		for data in data_list:
			data_mean = data_mean + data
		data_mean = data_mean / len(data_list)
		# 计算这组数据的方差
		data_variance = 0.0
		for data in data_list:
			data_variance = data_variance + pow((data - data_mean), 2)
		data_variance = data_variance / len(data_list)
		# 计算这组数据的标准差
		data_standard_deviation = sqrt(data_variance)
		# 将train_x_data_set的相关数据标准化
		for subscript, one_data in enumerate(train_x_data_set):
			train_x_data_set[subscript][index] = (one_data[index] - data_mean) / data_standard_deviation
	return


# 除了上面的z_score标准化,还写了一个0-1_normalization有兴趣可以两种标准化方法都试一下
# def zero_one_normalization():
# 	global train_x_data_set
# 	x_min = 0.0
# 	x_max = 0.0
# 	for index in range(len(train_x_data_set[0])):
# 		for one_data in train_x_data_set:
# 			if one_data[index] > x_max:
# 				x_max = one_data[index]
# 			elif one_data[index] < x_min:
# 				x_min = one_data[index]
# 		for subscript, one_data in enumerate(train_x_data_set):
# 			train_x_data_set[subscript][index] = (one_data[index] - x_min) / (x_max - x_min)
# 	return


data_z_score_standardization()


# 测试
# for i in range(len(train_x_data_set)):
# 	print(train_x_data_set[i])


# 返回batch_size个样本的下标,即我们抽取的用于训练的样本的下标
def get_next_batch(bt_size, x_data_set):
	bt_index = np.arange(len(x_data_set))
	# 设一个随机数种子,这样每次打乱时的顺序是一样的,除非修改种子值
	np.random.seed(2401)
	# 打乱数据的下标的次序
	np.random.shuffle(bt_index)
	# 取出bt_size个下标值
	bt_index = bt_index[0:bt_size]
	return bt_index


batch_size = 320
# 注意get_next_batch函数中取样本是随机取的,每次取的结果不一样,会导致每次用样本训练的结果也会不同,我们这里用了随机数种子使每次随机的结果固定
batch_index = get_next_batch(batch_size, train_x_data_set)

# y=b+w0*x0+w1*x1+w2*x2+w3*x3+w4*x4+w5*x5+w6*x6+w7*x7+w8*x8+w9*x9+w10*x10+w11*x11+w12*x12+w13*x13
# 先进行网格搜索,找出一组比较适合作为初始w和b的值
X = np.arange(-1.25, 1.25, step=0.0025)
# 生成1000个b的值
Y = np.arange(-0.7, 0.7, step=0.0001)
# 生成1000组w的值,每组w有14个值w0-w13
cross_entropy = np.zeros((len(X), len(X)))
# z是一个值全为0,len(X)行len(X)列的矩阵,即1000组w*1000组b的不同组合,每种组合都计算一下它们的cross_entropy值(样本为batch_index)
b_min = 0.0
w_min = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
cross_entropy_min = 10000
for i in range(len(X)):
	for j in range(len(X)):
		# 双层for循环,手动计算方差和的平均值
		b = X[i]
		w = Y[j * 14:j * 14 + 14]
		cross_entropy[i][j] = 0.0
		for key in batch_index:
			# 计算每种w和b的组合的cross_entropy值
			y_pred = b + w[0] * train_x_data_set[key][0] + w[1] * train_x_data_set[key][1] + w[2] * \
					 train_x_data_set[key][2] + w[3] * train_x_data_set[key][3] + w[4] * train_x_data_set[key][4] + w[
						 5] * train_x_data_set[key][5] + w[6] * train_x_data_set[key][6] + w[7] * train_x_data_set[key][
						 7] + w[8] * train_x_data_set[key][8] + w[9] * train_x_data_set[key][9] + +w[10] * \
					 train_x_data_set[key][10] + w[11] * train_x_data_set[key][11] + w[12] * train_x_data_set[key][12] + \
					 w[13] * train_x_data_set[key][13]
			y_pred = sigmoid(y_pred)
			cross_entropy[i][j] = cross_entropy[i][j] - (
					train_y_data_set[key] * log(y_pred) + (1 - train_y_data_set[key]) * log(1 - y_pred))
		# 最后求得的是这一组样本的交叉熵的和的平均值
		cross_entropy[i][j] = cross_entropy[i][j] / batch_size
		print(cross_entropy[i][j])
		print(w, b)
		# 下方的两个break是临时加的,目的是为了只计算一次最内层循环看一下一次循环计算的结果
		if cross_entropy[i][j] < cross_entropy_min:
			cross_entropy_min = cross_entropy[i][j]
			b_min = b
			w_min[0:] = w[0:]
		print("最佳参数:cross_entropy_min={}\nw_min={},b_min={}".format(cross_entropy_min, w_min, b_min))

运行这段代码,没有等到它运行完时,我们就发现cross_entropy_min已经很长时间不变了,我们取出这组cross_entropy_min对应的w和b值:

最佳参数:cross_entropy_min=0.5318484553883494
w_min=[0.11619999999991015, 0.11629999999991014, 0.11639999999991013, 0.11649999999991012, 0.11659999999991011, 0.1166999999999101, 0.11679999999991009, 0.11689999999991008, 0.11699999999991006, 0.11709999999991005, 0.11719999999991004, 0.11729999999991003, 0.11739999999991002, 0.11749999999991001],b_min=-1.207500000000001

我们可以根据上面的值将网络搜索的范围缩小,再搜索一次,即修改其中几行代码变成下面的形式:

X = np.arange(-1.3, -1.1, step=0.0002)
Y = np.arange(0.04, 0.18, step=0.00001)

再次运行上面的代码,运行一段时间后,待cross_entropy_min不再变化时,就可以取出这组cross_entropy_min对应的w和b值(不用等代码运行完)

最佳参数:cross_entropy_min=0.5318789944169704
w_min=[0.11812000000002393, 0.11813000000002394, 0.11814000000002392, 0.11815000000002393, 0.11816000000002394, 0.11817000000002395, 0.11818000000002393, 0.11819000000002394, 0.11820000000002395, 0.11821000000002396, 0.11822000000002394, 0.11823000000002395, 0.11824000000002396, 0.11825000000002397],b_min=-1.2336000000000074

如我们取到了上面一组参数。将这组w和b值作为初始的w和b。

使用MBGD优化方法和z_score标准化的logistic regression模型:

首先处理输入数据,我们可以看到对于每一个样本一共有15个不同类的数据。对于连续值的数据,我们可以直接将其变为float型录入,对于离散值的数据(很多类的那种),我们采用字典将其转换成float数值;

处理好数据后,得到train_x_data_set,train_y_data_set。由于train_x_data_set中有一些数据的量纲很大,因此我们要先使用函数data_z_score_standardization将其归一化;

使用get_next_batch函数取出一定数量的随机样本用于训练,注意这里设定了种子,种子值不变则每次随机的结果一样;

定义线性函数linear_function,交叉熵损失函数cross_entropy_function,优化方法函数(这里使用MBGD优化方法)gd_update_b_grad_and_w_grad_and_w_and_b;

定义初始w、b、lr、iteration,注意w和b是由上面网格搜索得到的;

训练模型iteration次,记录每次的cross_entropy、w、b,并得出一组最好的b_train_best,w_train_best;

画一个cross_entropy值随时间变化的图像;

使用get_next_batch函数取出一定数量的随机样本用于测试,注意这里每次的种子都不一样;

定义一个accuracy函数计算准确率;

进行test_iteration轮测试,计算每轮测试的准确率。

代码如下:
 

from math import exp, log, pow, sqrt
import numpy as np
import matplotlib.pyplot as plt
import random
import csv

# 有限责任公司(Self-emp-inc),无限责任公司(Self-emp-not-inc),个人(Private),联邦政府(Federal-gov),州政府(State-gov),
#  地方政府( Local-gov),无工作经验人员(Never-worked),无薪人员(Without-pay)
work_class = {
	'Self-emp-inc': 1.0, 'Self-emp-not-inc': 2.0, 'Private': 3.0, 'Federal-gov': 4.0, 'State-gov': 5.0,
	'Local-gov': 6.0,
	'Never-worked': 7.0, 'Without-pay': 8.0, '?': 0.0}
# 教育情况:Bachelors, Some-college, 11th, HS-grad, Prof-school, Assoc-acdm, Assoc-voc, 9th, 7th-8th, 12th, Masters,
# 1st-4th, 10th, Doctorate, 5th-6th, Preschool 
education = {'Bachelors': 1.0, 'Some-college': 2.0, '11th': 3.0, 'HS-grad': 4.0, 'Prof-school': 5.0, 'Assoc-acdm': 6.0,
			 'Assoc-voc': 7.0, '9th': 8.0, '7th-8th': 9.0, '12th': 10.0,
			 'Masters': 11.0, '1st-4th': 12.0, '10th': 13.0, 'Doctorate': 14.0, '5th-6th': 15.0, 'Preschool': 16.0}
# 已婚(Married-civ-spouse),再婚(Married-AF-spouse),已婚配偶缺席(Married-spouse-absent),离婚(Divorced)
# 离异(Separated),丧偶(Widowed),未婚(Never-married)
marital_status = {'Married-civ-spouse': 1.0, 'Married-AF-spouse': 2.0, 'Married-spouse-absent': 3.0, 'Divorced': 4.0,
				  'Separated': 5.0, 'Widowed': 6.0, 'Never-married': 7.0}
# 职业(Occupation):清洁工(Handlers-cleaners),维修工艺(Craft-repair),服务行业(Other-service), 销售(Sales),机床操控人员(Machine-op-inspct),
# 执行管理(Exec-managerial), 专业教授(Prof-specialty),技术支持(Tech-support),行政文员(Adm-clerical),
# 养殖渔业(Farming-fishing), 运输行业(Transport-moving),私人房屋服务(Priv-house-serv),
# 保卫工作(Protective-serv), 武装部队(Armed-Forces)
occupation = {'Handlers-cleaners': 1.0, 'Craft-repair': 2.0, 'Other-service': 3.0, 'Sales': 4.0,
			  'Machine-op-inspct': 5.0,
			  'Exec-managerial': 6.0, 'Prof-specialty': 7.0, 'Tech-support': 8.0, 'Adm-clerical': 9.0,
			  'Farming-fishing': 10.0,
			  'Transport-moving': 11.0, 'Priv-house-serv': 12.0, 'Protective-serv': 13.0, 'Armed-Forces': 14.0,
			  '?': 0.0}
# 妻子(Wife),子女(Own-child),丈夫(Husband),外来人员(Not-in-family)、 其他亲戚(Other-relative)、 未婚(Unmarried)
relationship = {'Wife': 1.0, 'Husband': 2.0, 'Own-child': 3.0, 'Unmarried': 4.0, 'Other-relative': 5.0,
				'Not-in-family': 6.0}
# 白人(White),亚洲太平洋岛民(Asian-Pac-Islander),阿米尔-印度-爱斯基摩人(Amer-Indian-Eskimo)、 其他(Other),黑人(Black)
race = {'White': 1.0, 'Black': 2.0, 'Asian-Pac-Islander': 3.0, 'Amer-Indian-Eskimo': 4.0, 'Other': 5.0}
sex = {'Female': 1.0, 'Male': 2.0}
# 美国(United-States)、 柬埔寨(Cambodia)、 英国(England),波多黎各(Puerto-Rico),加拿大(Canada),德国(Germany)
# 美国周边地区(关岛-美属维尔京群岛等)(Outlying-US(Guam-USVI-etc)),印度(India)、 日本(Japan)、 希腊(Greece)
# 美国南部(South)、 中国(China)、 古巴(Cuba)、 伊朗(Iran)、 洪都拉斯(Honduras),菲律宾(Philippines)
# 意大利(Italy)、 波兰(Poland)、 牙买加(Jamaica)、 越南(Vietnam)、 墨西哥(Mexico)、 葡萄牙(Portugal)
# 爱尔兰(Ireland)、 法国(France)、多米尼加共和国(Dominican-Republic)、 老挝(Laos)、 厄瓜多尔(Ecuador)
# 台湾(Taiwan)、 海地(Haiti)、 哥伦比亚(Columbia)、 匈牙利(Hungary)、 危地马拉(Guatemala)、 尼加拉瓜(Nicaragua)
# 苏格兰(Scotland)、 泰国(Thailand)、 南斯拉夫(Yugoslavia),萨尔瓦多(El-Salvador)、 特立尼达和多巴哥(Trinadad&Tobago)
# 秘鲁(Peru),香港(Hong),荷兰(Holland-Netherlands)

Nation_country = {'United-States': 1.0, 'Cambodia': 2.0, 'England': 3.0, 'Puerto-Rico': 4.0, 'Canada': 5.0,
				  'Germany': 6.0, 'Outlying-US(Guam-USVI-etc)': 7.0, 'India': 8.0, 'Japan': 9.0, 'Greece': 10.0,
				  'South': 11.0, 'China': 12.0, 'Cuba': 13.0, 'Iran': 14.0, 'Honduras': 15.0, 'Philippines': 16.0,
				  'Italy': 17.0, 'Poland': 18.0, 'Jamaica': 19.0, 'Vietnam': 20.0, 'Mexico': 21.0, 'Portugal': 22.0,
				  'Ireland': 23.0, 'France': 24.0, 'Dominican-Republic': 25.0, 'Laos': 26.0, 'Ecuador': 27.0,
				  'Taiwan': 28.0, 'Haiti': 29.0, 'Columbia': 30.0, 'Hungary': 31.0, 'Guatemala': 32.0,
				  'Nicaragua': 33.0, 'Scotland': 34.0, 'Thailand': 35.0, 'Yugoslavia': 36.0, 'El-Salvador': 37.0,
				  'Trinadad&Tobago': 38.0, 'Peru': 39.0, 'Hong': 40.0, 'Holand-Netherlands': 41.0, '?': 0.0}
income = {'>50K': 1.0, '<=50K': 0.0}

train_x_data_set = []
train_y_data_set = []

# 从原始数据中提取出用于train的x数据集和y数据集
with open('train.csv', 'r', encoding='UTF-8', errors='ignore') as csv_file:
	all_lines = csv.reader(csv_file)
	# 遍历train.csv的所有行
	for one_line in all_lines:
		if one_line[0] == 'age':
			continue
		one_line_x_data = []
		one_line_y_data = 0.0
		for i, element in enumerate(one_line):
			# 去除字符串首尾的空格
			element = element.strip()
			if i == 1:
				one_line_x_data.append(work_class[element])
			elif i == 3:
				one_line_x_data.append(education[element])
			elif i == 5:
				one_line_x_data.append(marital_status[element])
			elif i == 6:
				one_line_x_data.append(occupation[element])
			elif i == 7:
				one_line_x_data.append(relationship[element])
			elif i == 8:
				one_line_x_data.append(race[element])
			elif i == 9:
				one_line_x_data.append(sex[element])
			elif i == 13:
				one_line_x_data.append(Nation_country[element])
			elif i == 14:
				one_line_y_data = income[element]
			else:
				one_line_x_data.append(float(element))
		train_x_data_set.append(one_line_x_data)
		train_y_data_set.append(one_line_y_data)


# 测试,一共32561组样本
# print(len(train_x_data_set))
# print(len(train_y_data_set))

# 测试
# for i in range(len(train_x_data_set)):
# 	print(train_x_data_set[i])
# 	print(train_y_data_set[i])


# 对数据作z_score标准化处理,注意,我对所有项的数据都进行了z_score标准化,原因是有几项数据的数量级太大,直接计算时sigmoid函数计算会溢出
def data_z_score_standardization(x_data_set):
	# 对x_data_set中所有的数据都进行归一化
	for index in range(len(x_data_set[0])):
		# 取出需要归一化的一组数据
		data_list = []
		for one_data in x_data_set:
			data_list.append(one_data[index])
		# 计算这组数据的平均值
		data_mean = 0.0
		for data in data_list:
			data_mean = data_mean + data
		data_mean = data_mean / len(data_list)
		# 计算这组数据的方差
		data_variance = 0.0
		for data in data_list:
			data_variance = data_variance + pow((data - data_mean), 2)
		data_variance = data_variance / len(data_list)
		# 计算这组数据的标准差
		data_standard_deviation = sqrt(data_variance)
		# 将train_x_data_set的相关数据标准化
		for subscript, one_data in enumerate(x_data_set):
			x_data_set[subscript][index] = (one_data[index] - data_mean) / data_standard_deviation
	return x_data_set


# 定义sigmoid函数对output值作处理
def sigmoid(out):
	out = 1.0 / (1 + exp(-out))
	return out


# 除了上面的z_score标准化,还写了一个0-1_normalization有兴趣可以两种标准化方法都试一下
# def zero_one_normalization(x_data_set):
# 	x_min = 0.0
# 	x_max = 0.0
# 	for index in range(len(x_data_set[0])):
# 		for one_data in x_data_set:
# 			if one_data[index] > x_max:
# 				x_max = one_data[index]
# 			elif one_data[index] < x_min:
# 				x_min = one_data[index]
# 		for subscript, one_data in enumerate(x_data_set):
# 			x_data_set[subscript][index] = (one_data[index] - x_min) / (x_max - x_min)
# 	return x_data_set


train_x_data_set = data_z_score_standardization(train_x_data_set)


# 测试
# for i in range(len(train_x_data_set)):
# 	print(train_x_data_set[i])


# 返回batch_size个样本的下标,即我们抽取的用于训练的样本的下标
def get_next_batch(bt_size, x_data_set, sed):
	bt_index = np.arange(len(x_data_set))
	# 设一个随机数种子,这样每次打乱时的顺序是一样的,除非修改种子值
	np.random.seed(sed)
	# 打乱数据的下标的次序
	np.random.shuffle(bt_index)
	# 取出bt_size个下标值
	bt_index = bt_index[0:bt_size]
	return bt_index


train_batch_size = 320
# 取样本
# 注意get_next_batch函数中取样本是随机取的,每次取的结果不一样,会导致每次用样本训练的结果也会不同,我们这里用了随机数种子使每次随机的结果固定
batch_train_index = get_next_batch(train_batch_size, train_x_data_set, 2401)


# 定义线性函数
def linear_function(bdata, wdata, key, x_data_set):
	y = bdata + wdata[0] * x_data_set[key][0] + wdata[1] * x_data_set[key][1] + wdata[2] * \
		x_data_set[key][2] + wdata[3] * x_data_set[key][3] + wdata[4] * \
		x_data_set[key][4] + wdata[5] * x_data_set[key][5] + wdata[6] * \
		x_data_set[key][6] + wdata[7] * x_data_set[key][7] + wdata[8] * \
		x_data_set[key][8] + wdata[9] * x_data_set[key][9] + wdata[10] * \
		x_data_set[key][10] + wdata[11] * x_data_set[key][11] + wdata[12] * \
		x_data_set[key][12] + wdata[13] * x_data_set[key][13]
	return y


# 定义cross_entropy函数,bt_index即输入的x数据的下标,wdata即设定的w0-w13,bdata即设定的b
def cross_entropy_function(bt_index, x_data_set, y_data_set, wdata, bdata):
	crs_entropy = 0.0
	for index in bt_index:
		# 计算一组样本的cross_entropy的平均值
		y_p = linear_function(bdata, wdata, index, x_data_set)
		y_p = sigmoid(y_p)
		crs_entropy = crs_entropy - (
				y_data_set[index] * log(y_p) + (1 - y_data_set[index]) * log(1 - y_p))
	return crs_entropy / len(bt_index)


# 定义b的初始值
b = -1.2336000000000074
# 定义w0-w13的初始值
w = [0.11812000000002393, 0.11813000000002394, 0.11814000000002392, 0.11815000000002393, 0.11816000000002394,
	 0.11817000000002395, 0.11818000000002393, 0.11819000000002394, 0.11820000000002395, 0.11821000000002396,
	 0.11822000000002394, 0.11823000000002395, 0.11824000000002396, 0.11825000000002397]
# 注意上面的初始值是我们进行网络搜索时找到的一组cross_entropy值较小的值
# 定义学习速率lr的初始值,lr的值设置可以参照我们模拟手动计算时设置的步长,再设小一些
lrw, lrb = 0.00000001, 0.0000001
# 定义训练次数iteration
train_iteration = 2000
# 定义b_history、w_history、loss_history用来存储训练过程中更新的w、b、和loss
b_history = [b]
w_history = [w]
# loss_history初始值需要计算,用初始的w和b输入函数loss_function1进行计算
cross_entropy_history = [cross_entropy_function(batch_train_index, train_x_data_set, train_y_data_set, w, b)]

# 定义w偏导数和b的偏导数初始值为0
b_grad = 0.0
w_grad = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]


# 定义更新w的偏导数、b的偏导数、w和b的函数
# bgrad即w的偏导数,bgrad即b的偏导数,bt_index即输入的x数据的下标组,wdata即设定的w0-w13,bdata即设定的b
def gd_update_b_grad_and_w_grad_and_w_and_b(bgrad, wgrad, bt_index, x_data_set, y_data_set, wdata, bdata):
	# bt_index的元素个数即用于更新w和b的这批样本的数量,即表示了我们用多大规模的数据来更新一次w和b的梯度
	# 梯度即cross_entropy函数的关于w和b的所有偏导数
	for index in bt_index:
		bgrad = bgrad - y_data_set[index] * (1 - sigmoid(linear_function(bdata, wdata, index, x_data_set))) * 1.0 + (
				1 - y_data_set[index]) * sigmoid(linear_function(bdata, wdata, index, x_data_set)) * 1.0
		for subscript in range(len(w_grad)):
			wgrad[subscript] = wgrad[subscript] - y_data_set[index] * (
					1 - sigmoid(linear_function(bdata, wdata, index, x_data_set))) * x_data_set[index][subscript] + (
									   1 - y_data_set[index]) * sigmoid(
				linear_function(bdata, wdata, index, x_data_set)) * x_data_set[index][subscript]
	# 用偏导数乘以学习速率来更新w和b
	bdata = bdata - lrb * bgrad
	for subscript in range(len(w_grad)):
		wdata[subscript] = wdata[subscript] - lrw * w_grad[subscript]
	return bdata, wdata


# 测试
# w_out, b_out = gd_update_b_grad_and_w_grad_and_w_and_b(b_grad, w_grad, batch_index, train_x_data_set, train_y_data_set,
# 													   w, b)
# print(w_out, b_out)

# 存储训练得出的最好的w和b值,并记录此时的training error
b_train_best = 0.0
w_train_best = []
training_error_best = 10000.0

# 训练模型
for i in range(train_iteration):
	b_temp, w_temp = gd_update_b_grad_and_w_grad_and_w_and_b(b_grad, w_grad, batch_train_index, train_x_data_set,
															 train_y_data_set, w, b)
	# 每训练一次存储更新的w和b
	# 并且将更新的w和b的值赋值给w和b变量
	# 也就是说每训练一次更新了4个变量:b_grad、w_grad、w、b,b_grad和w_grad直接在函数体内更新,w和b要记录更新后的值,所以作为函数返回值
	cross_entropy_history.append(
		cross_entropy_function(batch_train_index, train_x_data_set, train_y_data_set, w_temp, b_temp))
	if cross_entropy_history[-1] < training_error_best:
		training_error_best = cross_entropy_history[-1]
		b_train_best = b_temp
		w_train_best.clear()
		[w_train_best.append(i) for i in w_temp]
	b_history.append(b_temp)
	w_history.append(w_temp)
	print("iteration:{} cross_entropy={} w={} b={}".format(i, cross_entropy_history[-1], w_temp, b_temp))
# 得到训练出来的最佳w、b、Loss
print(training_error_best, b_train_best, w_train_best)

# 创建一个图像
x_axis = []
for i in range(train_iteration):
	x_axis.append(i)
x_axis.append(train_iteration)
plt.plot(x_axis, cross_entropy_history, 'o-', markersize=3, linewidth=1.5, color='black')
# # 'o-'中o表示实心圈标记,-表示实线
plt.xlim()
plt.ylim()
plt.xlabel(r'$train_iteration$', fontsize=16)
plt.ylabel(r'$train_cross_entropy$', fontsize=16)
# 设置x轴和y轴的标签和标签的大小
plt.show()
# 显示图像

# 处理测试文件test.csv,得到测试集的x数据
test_x_data_set = []
with open('test.csv', 'r', encoding='UTF-8', errors='ignore') as csv_file:
	all_lines = csv.reader(csv_file)
	# 遍历train.csv的所有行
	for one_line in all_lines:
		if one_line[0] == 'age':
			continue
		one_line_x_data = []
		for i, element in enumerate(one_line):
			# 去除字符串首尾的空格
			element = element.strip()
			if i == 1:
				one_line_x_data.append(work_class[element])
			elif i == 3:
				one_line_x_data.append(education[element])
			elif i == 5:
				one_line_x_data.append(marital_status[element])
			elif i == 6:
				one_line_x_data.append(occupation[element])
			elif i == 7:
				one_line_x_data.append(relationship[element])
			elif i == 8:
				one_line_x_data.append(race[element])
			elif i == 9:
				one_line_x_data.append(sex[element])
			elif i == 13:
				one_line_x_data.append(Nation_country[element])
			else:
				one_line_x_data.append(float(element))
		test_x_data_set.append(one_line_x_data)

# 处理测试文件test.csv,得到测试集的x数据
test_y_data_set = []
with open('correct_answer.csv', 'r', encoding='UTF-8', errors='ignore') as csv_file:
	all_lines = csv.reader(csv_file)
	# 遍历train.csv的所有行
	for one_line in all_lines:
		if one_line[0] == 'id':
			continue
		one_line_y_data = 0.0
		for i, element in enumerate(one_line):
			# 去除字符串首尾的空格
			element = element.strip()
			if i == 0:
				continue
			elif i == 1:
				one_line_y_data = one_line_y_data + float(element)
		test_y_data_set.append(one_line_y_data)

# 测试用的x数据也要归一化
test_x_data_set = data_z_score_standardization(test_x_data_set)
test_iteration = 3
test_batch_size = 320


# 计算模型预测的准确率
def accuracy(bt_index, x_data_set, y_data_set, wdata, bdata):
	y_l_list = []
	res_list = []
	for index in bt_index:
		# 计算一组样本的cross_entropy的平均值
		y_p = linear_function(bdata, wdata, index, x_data_set)
		y_p = sigmoid(y_p)
		y_l = 0.0
		res = 0.0
		if y_p >= 0.5:
			y_l = 1.0
		if y_l == y_data_set[index]:
			res = 1.0
		res_list.append(res)
		y_l_list.append(y_l)
	acr = 0.0
	for res in res_list:
		acr = acr + res
	acr = acr / len(bt_index)
	return y_l_list, acr


# 测试
# seed_value = random.randint(0, 10000)
# batch_test_index = get_next_batch(test_batch_size, test_x_data_set, seed_value)
# y_pred_label_list, acc = accuracy(batch_test_index, test_x_data_set, test_y_data_set, w_train_best, b_train_best)
# print(y_pred_label_list)
# print(acc)

for i in range(test_iteration):
	# 取一批测试样本
	seed_value = random.randint(0, 10000)
	batch_test_index = get_next_batch(test_batch_size, test_x_data_set, seed_value)
	# 计算该批样本预测的准确率
	y_pred_label_list, acc = accuracy(batch_test_index, test_x_data_set, test_y_data_set, w_train_best, b_train_best)
	# 计算该批样本的cross_entropy
	test_cross_entropy = cross_entropy_function(batch_test_index, test_x_data_set, test_y_data_set, w_train_best,
												b_train_best)
	num = 0
	for j in batch_test_index:
		print("样本编号:{} 预测标签:{} 真实标签:{}".format(j, y_pred_label_list[num], test_y_data_set[j]))
		num = num + 1
	print("test_iteration:{}\n acc:{}\n test_cross_entropy:{}".format(i, acc, test_cross_entropy))

运行结果如下:

生成的训练时的cross_entropy值的变化图。

得到的最佳参数cross_entropy、b和w:

0.3851662645508392 -1.233601624702985 [0.39661073902905447, 0.057156146772790635, 0.06768751743168605, 0.09053575022632332, 0.6186871098635961, -0.43385310606433936, 0.15738231823194487, -0.26448422319592063, -0.0567938200499726, 0.3312379050966214, 0.3390591816951852, 0.13720501938348878, 0.41144536493112943, -0.11103552637912766]

测试结果如下(测试代码即在训练模型的代码之后):

test_iteration:0
 acc:0.796875
 test_cross_entropy:0.4040875069037342

test_iteration:1
 acc:0.821875
 test_cross_entropy:0.3909683414258476

test_iteration:2
 acc:0.81875
 test_cross_entropy:0.3850121737082529

Process finished with exit code 0

可以看到准确率大概在0.8左右。因为这次的模型只是为了自己实现一下logistic regression的相关原理,其model和local minima值找的不是很好,导致准确率不算太高。有兴趣的同学可以改动一下model(比如改成更高次的model)和网格搜索的范围,找一组更好的model和local minima值试一下。本文主要是为了用代码来实现logistic regression模型。

猜你喜欢

转载自blog.csdn.net/zgcr654321/article/details/83820754
今日推荐