1. 程式人生 > >李巨集毅機器學習 P12 HW2 Winner or Loser 筆記(不使用框架實現使用MBGD優化方法和z_score標準化的logistic regression模型)

李巨集毅機器學習 P12 HW2 Winner or Loser 筆記(不使用框架實現使用MBGD優化方法和z_score標準化的logistic regression模型)

建立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%的資料。注意取樣本時我們採用了隨機數,取樣本時是隨機的。

程式碼如下:

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模型。