操作及代码解读
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from sklearn.linear_model import LinearRegression
#线性回归用 sklearn.linear_model
#读表格数据用 pandas
#数据可视化用 matplotlib.pyplot
读取数据并定义自变量X,y
lifesat = pd.read_csv("data/lifesat.csv")
X = lifesat[["GDP per capita (USD)"]].values
y = lifesat[["Life satisfaction"]].values
# 数据可视化 visualize the data
lifesat.plot(kind='scatter', grid=True,
x="GDP per capita (USD)", y="Life satisfaction")
lifesat.plot(…): 这里 lifesat 是一个 Pandas DataFrame 对象,plot() 函数将在这个 DataFrame 上创建一个可视化图表。
kind=‘scatter’: 这个参数指定了图表的类型为散点图。
grid=True: 这个参数设置在图表上显示网格线,方便读取数据。
x=“GDP per capita (USD)”: 这个参数指定了 x 轴所代表的数据列,也就是"人均国内生产总值(美元)"。
y=“Life satisfaction”: 这个参数指定了 y 轴所代表的数据列,也就是"生活满意度"。
plt.axis([23_500, 62_500, 4, 9]) #范围 x(23500,62500),y(4,9)
plt.show()
规定范围并画图
您如何知道哪些值将使您的模型表现得最好?要回答这个问题,您需要指定一个性能度量值。您可以定义一个效用函数(或适应度函数),也可以定义一个衡量模型好坏的成本函数。对于线性回归问题,人们通常使用一个成本函数来度量线性模型的预测和训练例子之间的距离;目标是使这个距离最小化。
model = LinearRegression()
model.fit(X, y)
建立模型并训练X 和y
X_new = [[37_655.2]] # Cyprus' GDP per capita in 2020
print(model.predict(X_new))
假设一个X_new的值 可以求预测y值
x = np.linspace(23500, 62500, 2).reshape(-1, 1)
y = model.predict(x)
x = x.reshape(-1)
y = y.reshape(-1)
线性回归求y值时需要注意的
plt.plot(x, y, c='red')
将两个图结合在一起
another way: KNeighborsRegressor
K-Nearest Neighbors (KNN) 回归算法。这个代码片段定义了一个名为 model 的 KNeighborsRegressor 对象,其中 n_neighbors=3 表示使用 3 个最近邻居来进行预测。
model = KNeighborsRegressor(n_neighbors=3)
model.fit(X, y)
print(model.predict(X_new)) # outputs [[6.33333333]]
from pathlib import Path
datapath = Path() / "datasets" / "lifesat"
datapath.mkdir(parents=True, exist_ok=True)
这段代码首先创建了一个名为datapath的路径对象,该路径指向一个名为"datasets"的文件夹,其中包含一个名为"lifesat"的子文件夹。接着,使用mkdir()方法创建了这两个文件夹。参数parents=True表示如果父文件夹不存在也会一并创建,exist_ok=True表示如果文件夹已经存在则不会引发错误。
datapath = Path() / "datasets" / "lifesat"
-
Path()
: 这部分使用了Python的Path类,通常来自于标准库中的pathlib
模块。Path类用于处理文件系统路径的对象,提供了许多便捷的方法来执行路径操作。如果Path()
函数没有传入任何参数,则创建的Path
对象将表示当前工作目录。 -
/ "datasets" / "lifesat"
: 这部分使用了路径操作符/
来连接多个路径部分。在这里,它将空的Path对象(即当前工作目录)与字符串"datasets"和"lifesat"连接起来,形成一个更具体的路径对象。 -
因此,
datapath = Path() / "datasets" / "lifesat"
表示创建了一个名为datapath
的路径对象,该对象指向当前工作目录下的 “datasets/lifesat” 目录。
data_root = "https://github.com/ageron/data/raw/main/"
for filename in ("oecd_bli.csv", "gdp_per_capita.csv"):
if not (datapath / filename).is_file():
print("Downloading", filename)
url = data_root + "lifesat/" + filename
print(url)
# urllib.request.urlretrieve(url, datapath / filename)
使用 for循环遍历包含文件名(“oecd_bli.csv”, “gdp_per_capita.csv”)的元组。
-
datapath / filename
: 这部分是使用路径操作符"/"将datapath路径对象与当前迭代的文件名(filename)拼接在一起,以获取完整的文件路径。 -
(datapath / filename).is_file()
: 这部分是对拼接后的完整文件路径调用is_file()方法,该方法用于检查该路径是否指向一个文件(而不是文件夹或其他类型的对象)。 -
if not (datapath / filename).is_file()
: 这部分是条件语句,如果拼接后的完整文件路径不指向一个文件(即文件不存在),则条件为True,代码块内的操作将被执行。
.is_file()
: 检查该路径是否指向一个文件
oecd_bli = pd.read_csv(datapath / "oecd_bli.csv")
gdp_per_capita = pd.read_csv(datapath / "gdp_per_capita.csv")
使用pd.read_csv()
函数来读取两个csv文件的数据,并分别存储在oecd_bli
和gdp_per_capita
变量中,以便后续对这些数据进行分析和处理
gdp_year = 2020 #选取需要参考的年份
gdppc_col = "GDP per capita (USD)"
lifesat_col = "Life satisfaction"
#对存储在gdp_per_capita 的数据进行更改
gdp_per_capita = gdp_per_capita[gdp_per_capita['Year'] == gdp_year] #制定年份的数据
gdp_per_capita = gdp_per_capita.drop(["Code", "Year"], axis=1) #删除 Code 和 Year 两列数据
gdp_per_capita.columns = ["Country", gdppc_col]#修改列命名
gdp_per_capita.set_index("Country", inplace=True) #改变索引,将原本的数字改成Country
gdp_per_capita.head() #输出前五项
.drop()
表示删除
axis = 1
表示垂直方向删除两栏数据 Code Year
gdp_per_capita.columns
表示数据框的列名字
这行代码将 gdp_per_capita
数据框的列名重命名为 “Country” 和 gdppc_col
的值
set_index()
函数:
- 这个函数用于将指定的列设置为数据框的索引列。
- 在这个例子中,将 “Country” 列设置为新的索引。
最后,gdp_per_capita.head()
输出数据框的前五项
oecd_bli = oecd_bli[oecd_bli["INEQUALITY"]=="TOT"] #同上选制定年份
oecd_bli = oecd_bli.pivot(index="Country", columns="Indicator", values="Value")
#将行列索引分别改成Indicator 和 Country,其值为Values栏的数据
oecd_bli.head()#输出前五项
oecd_bli = oecd_bli.pivot()
函数,将原来的数据框进行更改,在本代码中,改变了行索引和列索引及其值,对于研究更为方便,同时省去了不必要的数据,只留下了生活满意度
列。
最后输出前五项。
现在我们来合并生活满意度数据和人均GDP数据,只保留人均GDP和生活满意度列
full_country_stats = pd.merge(left=oecd_bli, right=gdp_per_capita,
left_index=True, right_index=True)
full_country_stats.sort_values(by=gdppc_col, inplace=True)
full_country_stats = full_country_stats[[gdppc_col, lifesat_col]]
full_country_stats.head()
full_country_stats = pd.merge(left=oecd_bli, right=gdp_per_capita, left_index=True, right_index=True)
将两个数据框合并 并且左边是 oecd_bli,右边是gdp_per_capita。且使用两个数据框的索引(行索引)来进行合并。
pd.merge()
函数合并数据框 (left = ,right = ,left_index = ,right_index = )
sort_values()
函数进行顺序排列(by= , inplace=True)
inplace = True
表示原地操作
full_country_stats = full_country_stats[[gdppc_col, lifesat_col]]
选定指定列
为了说明过拟合的风险,我在大多数数字中只使用了部分数据(所有人均GDP在min_gdp
到max_gdp
之间的国家)。在本章的后面,我揭示了缺失的国家,并表明它们根本不遵循相同的线性趋势。
min_gdp = 23_500
max_gdp = 62_500
country_stats = full_country_stats[(full_country_stats[gdppc_col] >= min_gdp) &
(full_country_stats[gdppc_col] <= max_gdp)]
country_stats.head()
&
表示并且,连接两个判断语句。
只需要full_country_stats[gdppc_col]
中的指定大小范围数据
country_stats.to_csv(datapath / "lifesat.csv")
full_country_stats.to_csv(datapath / "lifesat_full.csv")
to_csv()
是 Pandas 库中的一个方法,用于将数据保存为 CSV 文件格式。第一列将变量中的数据框保存为名为 lifesat.csv
的CSV文件,保存路径为 datapath
路径
import matplotlib.pyplot as plt
country_stats.plot(kind='scatter', figsize=(5, 3), grid=True,
x=gdppc_col, y=lifesat_col)#创建一个散点图,其中x表示gdppc_col,y表示lifesat_col,大小为(5, 3),grid=True表示画网格。
此时我们需要对图像进行加工,比如lifesat_col
的范围应该在(4,9)
min_life_sat = 4
max_life_sat = 9
position_text = {
"Turkey": (29_500, 4.2),
"Hungary": (28_000, 6.9),
"France": (40_000, 5),
"New Zealand": (28_000, 8.2),
"Australia": (50_000, 5.5),
"United States": (59_000, 5.3),
"Denmark": (46_000, 8.5)
}#定义了一个名为 position_text 的字典,其中存储了几个国家在散点图上的文本标注位置
for country, pos_text in position_text.items(): #遍历字典中所有地点和坐标
pos_data_x = country_stats[gdppc_col].loc[country]
#在数据框country_stats的gdppc_col栏选country变量对应的值
pos_data_y = country_stats[lifesat_col].loc[country]
country = "U.S." if country == "United States" else country
plt.annotate(country, xy=(pos_data_x, pos_data_y),
xytext=pos_text, fontsize=12,
arrowprops=dict(facecolor='black', width=0.5,
shrink=0.08, headwidth=5))
plt.plot(pos_data_x, pos_data_y, "ro") #标点点 "ro"中 r为red o 为circle
plt.axis([min_gdp, max_gdp, min_life_sat, max_life_sat])
plt.savefig(datapath'money_happy_scatterplot') #保存 括号填位置
plt.show()
.loc()
函数可用来通过索引选择数据
plt.annotate()
函数用于在 Matplotlib 的图形中添加注释。这个函数非常灵活,可以添加文本、箭头等。下面是您提供的代码中各个参数的意思:
-
country
: 这是要显示的注释文本,通常是一个字符串。在这个上下文中,它可能是一个国家的名称。 -
xy
: 这是一个元组,指定了注释文本要关联的数据点的坐标。pos_data_x
和pos_data_y
分别是这个点的 x 和 y 坐标。 -
xytext
: 这指定了注释文本的实际位置,如图中的Turkey。它是一个元组,包含了文本的 x 和 y 坐标。 -
fontsize
: 这设置了注释文本的字体大小。 -
arrowprops
: 这是一个字典,用于定义连接数据点和注释文本的箭头的样式。它包含以下键:facecolor
: 箭头的颜色。width
: 箭头的宽度。shrink
: 箭头两端距离数据点和文本的距离比例。这有助于避免箭头直接指向数据点或文本,从而看起来更美观。headwidth
: 箭头头部的宽度。
plt.axis()
函数 调整轴的参数([ min_x , max_x , min_y , max_y])
highlighted_countries = country_stats.loc[list(position_text.keys())]
highlighted_countries[[gdppc_col, lifesat_col]].sort_values(by=gdppc_col)
position_text.keys()
获取字典position_text
的所有键,list()
函数将这些键转换成一个列表。这些列表中的元素又作为.loc()
的索引。
.sort_values()
函数,按照括号内by = gdppc_col
大小顺序排列
可以看到,随着人均GDP的增长,生活满意度或多或少呈线性增长。所以你决定把生活满意度建模为人均GDP的线性函数。这一步被称为模型选择:你选择了一个生活满意度的线性模型,只有一个属性,即人均GDP
l
i
f
e
s
a
t
i
s
f
a
c
t
i
o
n
=
θ
0
+
θ
1
×
G
D
P
p
e
r
c
a
p
i
t
a
lifesatisfaction = θ_0 + θ_1 × GDPpercapita
lifesatisfaction=θ0+θ1×GDPpercapita
该模型有两个模型参数, θ 0 θ_0 θ0和 θ 1 θ_1 θ1。通过调整这些参数,您可以使您的模型表示任何线性函数
country_stats.plot(kind='scatter', figsize=(5, 3), grid=True,
x=gdppc_col, y=lifesat_col)
X = np.linspace(min_gdp, max_gdp, 1000)
w1, w2 = 4.2, 0
plt.plot(X, w1 + w2 * 1e-5 * X, "r")
plt.text(40_000, 4.9, fr"$\theta_0 = {w1}$", color="r")
plt.text(40_000, 4.4, fr"$\theta_1 = {w2}$", color="r")
w1, w2 = 10, -9
plt.plot(X, w1 + w2 * 1e-5 * X, "g")
plt.text(26_000, 8.5, fr"$\theta_0 = {w1}$", color="g")
plt.text(26_000, 8.0, fr"$\theta_1 = {w2} \times 10^{{-5}}$", color="g")
w1, w2 = 3, 8
plt.plot(X, w1 + w2 * 1e-5 * X, "b")
plt.text(48_000, 8.5, fr"$\theta_0 = {w1}$", color="b")
plt.text(48_000, 8.0, fr"$\theta_1 = {w2} \times 10^{{-5}}$", color="b")
plt.axis([min_gdp, max_gdp, min_life_sat, max_life_sat])
plt.show()
plt.text()
文字注释,plt.text(x , y , fr'' ,color )
fr
前缀用于创建一个原始字符串(raw string)
,同时允许你在字符串中嵌入表达式,也可以是
l
a
t
e
x
latex
latex公式
1e-5
表示
1
0
−
5
10^{-5}
10−5
在使用模型之前,您需要定义参数值 θ 0 θ_0 θ0和 θ 1 θ_1 θ1。您如何知道哪些值将使您的模型表现得最好?要回答这个问题,您需要指定一个性能度量值。您可以定义一个效用函数(或适应度函数),也可以定义一个衡量模型好坏的成本函数。对于线性回归问题,人们通常使用一个成本函数来度量线性模型的预测和训练例子之间的距离;目标是使这个距离最小化。类似求方差的最小二乘法。
这就是线性回归算法的作用所在:你给它提供你的训练示例,它就会找到使线性模型最适合你的数据的参数。这叫做训练模型。
from sklearn import linear_model
X_sample = country_stats[[gdppc_col]].values
y_sample = country_stats[[lifesat_col]].values
lin1 = linear_model.LinearRegression()
lin1.fit(X_sample, y_sample)
t0, t1 = lin1.intercept_[0], lin1.coef_[0][0]
print(f"θ0={t0:.2f}, θ1={t1:.2e}")
导入线性模型linear_model
模块
country_stats[[gdppc_col]]
和country_stats[[lifesat_col]]
都使用了双重方括号,这是因为当你从一个数据框中选择列时,即使你只选择一列,结果仍然是一个数据框。如果你想要得到一个Series(一维数组),你应该使用单重方括号,但在这里为了保持结果的一致性(即得到一个二维数组,即使它只有一列),使用了双重方括号。
.values
属性用于将DataFrame或Series中的数据转换为一个NumPy数组。因此,X_sample
和y_sample
都是NumPy数组,它们分别包含了“人均GDP”和“生活满意度”的数据。
lin1 = linear_model.LinearRegression()
创建回归模型对象
lin1.fit(X_sample, y_sample)
使用数据拟合模型
得到结果:lin1.intercept_[0]
截距(intercept)和 lin1.coef_[0][0]
系数(coefficient)
(coef_
是一个数组,包含模型的系数。对于简单线性回归,coef_
将只包含一个元素,即y = mx + b
中的m
。对于多元线性回归(多个自变量),coef_
将包含多个元素,每个元素对应一个自变量的系数。)
{t0:.2f}
表示t0
的值将被格式化为一个浮点数,且小数点后保留两位数字。
{t1:.2e}
表示t1
的值将被格式化为科学记数法,且小数点后保留两位数字。
在我们的例子中,算法发现最优参数值分别为θ0 = 3.75
和θ1 = 6.78×10-5
。
country_stats.plot(kind='scatter', figsize=(5, 3), grid=True,
x=gdppc_col, y=lifesat_col)
X = np.linspace(min_gdp, max_gdp, 1000)
plt.plot(X, t0 + t1 * X, "b")
plt.text(max_gdp - 20_000, min_life_sat + 1.9,
fr"$\theta_0 = {t0:.2f}$", color="b")
plt.text(max_gdp - 20_000, min_life_sat + 1.3,
fr"$\theta_1 = {t1 * 1e5:.2f} \times 10^{{-5}}$", color="b")
plt.axis([min_gdp, max_gdp, min_life_sat, max_life_sat])
plt.savefig(datapath/'best_fit_model_plot') #保存
plt.show()
现在这个模型尽可能接近训练数据
接下来预测数据 如Cyprus
cyprus_gdp_per_capita = gdp_per_capita[gdppc_col].loc["Cyprus"]
cyprus_gdp_per_capita#37655.1803457421
cyprus_predicted_life_satisfaction = lin1.predict([[cyprus_gdp_per_capita]])[0][0]
cyprus_predicted_life_satisfaction#6.301656332738056
.predict()
方法期望的输入是一个二维数组,即使只有一个预测样本。返回也是一个二维数组。通过索引的方法来获取数据而不是直接得到一个数组。
country_stats.plot(kind='scatter', figsize=(5, 3), grid=True,
x=gdppc_col, y=lifesat_col)
X = np.linspace(min_gdp, max_gdp, 1000)
plt.plot(X, t0 + t1 * X, "b") #作出散点图和预测直线
plt.text(min_gdp + 22_000, max_life_sat - 1.1,
fr"$\theta_0 = {t0:.2f}$", color="b")
plt.text(min_gdp + 22_000, max_life_sat - 0.6,
fr"$\theta_1 = {t1 * 1e5:.2f} \times 10^{{-5}}$", color="b")图像的标注
plt.plot([cyprus_gdp_per_capita, cyprus_gdp_per_capita],
[min_life_sat, cyprus_predicted_life_satisfaction], "r--")
plt.text(cyprus_gdp_per_capita + 1000, 5.0,
fr"Prediction = {cyprus_predicted_life_satisfaction:.2f}", color="r")
plt.plot(cyprus_gdp_per_capita, cyprus_predicted_life_satisfaction, "ro")
plt.axis([min_gdp, max_gdp, min_life_sat, max_life_sat])#调整坐标轴参数
plt.show()
plt.plot([],[],[])
第一个表示X的取值范围,[x_1,x_2]
。第二个表示y的取值范围。
第三个表示线的属性,如颜色'r'表示红色,'--表示虚线','o'表示圆圈
非代表性培训数据
为了很好地泛化,训练数据能够代表您想要泛化的新情况是至关重要的。无论您使用基于实例的学习还是基于模型的学习,情况都是如此。
在本例题中,如果我们选择在标准之外的数据,情况会如何呢?
missing_data = full_country_stats[(full_country_stats[gdppc_col] < min_gdp) |
(full_country_stats[gdppc_col] > max_gdp)]
missing_data
position_text_missing_countries = {
"South Africa": (20_000, 4.2),
"Colombia": (6_000, 8.2),
"Brazil": (18_000, 7.8),
"Mexico": (24_000, 7.4),
"Chile": (30_000, 7.0),
"Norway": (51_000, 6.2),
"Switzerland": (62_000, 5.7),
"Ireland": (81_000, 5.2),
"Luxembourg": (92_000, 4.7),
}
full_country_stats.plot(kind='scatter', figsize=(8, 3),
x=gdppc_col, y=lifesat_col, grid=True)
for country, pos_text in position_text_missing_countries.items():
pos_data_x, pos_data_y = missing_data.loc[country]
plt.annotate(country, xy=(pos_data_x, pos_data_y),
xytext=pos_text, fontsize=12,
arrowprops=dict(facecolor='black', width=0.5,
shrink=0.08, headwidth=5))
plt.plot(pos_data_x, pos_data_y, "ro")
X = np.linspace(0, 115_000, 1000)
plt.plot(X, t0 + t1 * X, "b--")
#标出超出范围的,不具有代表性的点,并画出原来的直线
lin_reg_full = linear_model.LinearRegression()
Xfull = np.c_[full_country_stats[gdppc_col]]
yfull = np.c_[full_country_stats[lifesat_col]]
lin_reg_full.fit(Xfull, yfull)
t0full, t1full = lin_reg_full.intercept_[0], lin_reg_full.coef_[0][0]
X = np.linspace(0, 115_000, 1000)
plt.plot(X, t0full + t1full * X, "k")
plt.axis([0, 115_000, min_life_sat, max_life_sat])
#画出整体的模型曲线
plt.show()
Xfull = np.c_[full_country_stats[gdppc_col]]
:这行代码使用NumPy的c_
函数
来将full_country_stats
中名为gdppc_col
的列作为X变量提取出来,并转换为二维数组。注意,这里使用了额外的方括号[]
来确保提取的列是二维的,即使它原本只有一列。
如果对此数据训练线性模型,则将得到实线,而旧模型用虚线表示。正如你所看到的,不仅添加一些缺失的国家显著地改变了模型,而且它清楚地表明,这样一个简单的线性模型可能永远不会很好地工作。似乎非常富裕的国家并不比中等富裕的国家更快乐(事实上,他们看起来有点不快乐!),相反,一些贫穷国家似乎比许多富裕国家更幸福。
使用一个能够代表您要泛化到的情况的训练集是至关重要的
过拟合训练数据
假设你要去国外旅游,出租车司机把你骗了。你可能会忍不住说,那个国家所有的出租车司机都是小偷。过度概括是我们人类经常要做的事情。在机器学习中,这被称为过拟合:这意味着该模型在训练数据上表现良好,但它不能很好地泛化
from sklearn import preprocessing
from sklearn import pipeline
full_country_stats.plot(kind='scatter', figsize=(8, 3),
x=gdppc_col, y=lifesat_col, grid=True)
poly = preprocessing.PolynomialFeatures(degree=10, include_bias=False)
# 最高次为10
scaler = preprocessing.StandardScaler()
lin_reg2 = linear_model.LinearRegression()
pipeline_reg = pipeline.Pipeline([
('poly', poly),
('scal', scaler),
('lin', lin_reg2)])
pipeline_reg.fit(Xfull, yfull)
curve = pipeline_reg.predict(X[:, np.newaxis])
plt.plot(X, curve)
plt.axis([0, 115_000, min_life_sat, max_life_sat])
plt.show()
preprocessing.PolynomialFeatures
是从 scikit-learn(一个流行的 Python 机器学习库)的预处理模块中导入的一个类,用于生成多项式特征。
degree=10
:这个参数指定了多项式的最高次数
include_bias=False
:这个参数指定是否包括偏置项(即常数项或截距项)。在这个例子中,它被设置为False