上篇波士顿房价预测使用了线性回归模型,适用于连续型目标变量的回归问题,其取值范围(-∞,+∞)。逻辑回归模型(Logistic回归模型)常用于二分类问题,比如有一些经典的二分类问题:
● 预测贷款违约状况(会违约/不会违约)
● 情感分析(正面/负面)
● 预测广告点击率(会点击/不会点击)
● 预测疾病(阳性/阴性)
我们本文讨论的预测乳腺癌是良性还是恶性。
1、数据集读取
威斯康星州乳腺癌数据集(简称cancer),里面记录了乳腺癌肿瘤的临床测量数据。每个肿瘤都被标记为“良性”(benign,表示无害肿瘤)或“恶 性”(malignant,表示癌性肿瘤),其任务是基于人体组织的测量数据来学习预测肿瘤是否为恶性。
该数据集直接内置在 scikit-learn 中,可以用scikit-learn 模块的load_breast_cancer 函数来加载数据(也可以从csv文件中读取):
from sklearn.datasets import load_breast_cancer
import matplotlib.pyplot as plt
import pandas as pd
from sklearn.model_selection import train_test_split
if __name__ == "__main__":
# 加载乳腺癌数据集
cancer_data = load_breast_cancer()
# 获取特征和标签
data = cancer_data.data
print('data.shape={}'.format(data.shape))
df = pd.DataFrame(data, columns=cancer_data['feature_names'])
print(df.head(5))
共计569条数据,每条数据有30个特征。依次是(下图值截取了5个特征),对我们来说不必要关心这些,只要食道有30个特征X就行
我们在看一下预测值target,并且我们为了方便阅读,把预测值target追加到了X特征的最后一列。
if __name__ == "__main__":
# 加载乳腺癌数据集
cancer_data = load_breast_cancer()
# 获取特征和标签
data = cancer_data.data
print('data.shape={}'.format(data.shape))
df = pd.DataFrame(data, columns=cancer_data['feature_names'])
print(df.head(5))
target = cancer_data.target # 预测值Y,取值都是0或者1,1良性,0恶性
df = pd.DataFrame(cancer_data['data'], columns=cancer_data['feature_names'])
df['target'] = cancer_data['target']
print(df.head())
1.1 训练集和测试集
# 我们将数据分为训练集和测试集,测试集占比0.2
x_train, x_test, y_train, y_test = train_test_split(data, target, test_size=0.2, random_state=0)
print("Shape of x_train:{},Shape of x_test:{}".format(x_train.shape, x_test.shape))
print("Shape of y_train:{},Shape of y_test:{}".format(y_train.shape, y_test.shape))
1.2 数据标准化处理
和之前做波斯顿的案例一样,数据需要进行标准化的处理(将所有的数据按照比例缩放到一定的范围内),如下使用sklearn.preprocessing import StandardScaler来做。 fit_transform是StandardScaler类中的一个方法,用于对数据进行拟合和标准化处理。
# 数据标准化处理
sc = StandardScaler()
x_train = sc.fit_transform(x_train)
y_train = sc.fit_transform(y_train.reshape(455, 1))
# 将数据封装成张量
x_train = torch.from_numpy(x_train.astype(np.float32))
y_train = torch.from_numpy(y_train.astype(np.float32))
x_test = torch.from_numpy(x_test.astype(np.float32))
y_test = torch.from_numpy(y_test.astype(np.float32))
y_train = y_train.view(y_train.shape[0], 1)
y_test = y_test.view(y_test.shape[0], 1)
print(y_train.size(), y_test.size())
讲一下fit_transform方法的作用,对给定的数据计算数据的均值和标准差。fit_transform方法将使用计算得到的均值和标准差对数据进行标准化处理。标准化的过程是将每个数据点减去均值,然后除以标准差,以确保数据具有零均值和单位方差。最后,fit_transform方法返回标准化后的数据。这样,你可以将标准化后的数据用于训练模型,以便在模型中使用一致的数据尺度。
2、模型设计
线性模型的形式为y = w*x + b,其中x可以是多维特征,逻辑回归模型的形式和线性模型一样,唯一不同的是,对于y会套一层Sigmoid函数,Sigmoid 的函数公式如下:
可将取值范围为(-∞,+∞)的数转换到(0,1)之间,刚刚好适合概率的表示,使用Python的numpy,matplotlib对该函数进行可视化,如下图所示:
可以看到该函数具有很好的数学性质,既可以用于预测类别,并且任意阶可微。如果预测值大于0.5的时候,相当于患病,小于0.5的时候,相当于不患病。
2.1 损失函数
接下来我们看下对于回归模型的损失函数:
对于任务一组数据X,经过w*x + b 得到y1,y1被作用Sigmoid函数得到的(是一个0-1之间的小数),表示这个数据属于第一类的概率,那么对于属于第二类的概率就是 1-y1,若我们希望某个X是属于第二类,那么y1希望越大越好,当y1=1时最佳。所以拆分来看的话:
如果y是1,属于第二类的概率是:
如果y是0,属于第一类的概率是:
def loss(y_predict, y):
return - (y * y_predict.clamp(1e-12).log() + (1 - y) * (1 - y_predict).clamp(1e-12).log()).mean()
这里为什么要用clamp呢,通过使用clamp()函数将值限制在一个安全范围内,可以确保计算过程中不会出现错误或者无效的结果。具体来说,(1e-12).log()的目的是将值限制在1e-12和正无穷大之间,并取它们的自然对数。这样做是为了计算y * y_predict.clamp(1e-12).log()这一部分的损失函数值。类似地,(1 - y) * (1 - y_predict).clamp(1e-12).log()的目的是将值限制在1e-12和正无穷大之间,并取它们的自然对数。这样做是为了计算(1 - y) * (1 - y_predict).clamp(1e-12).log()这一部分的损失函数值。
其实上面的分析,pytorch已经有相对应的损失函数了,即为二分类交叉熵损失函数BCELOSS
2.2 定义模型
class MyModel(torch.nn.Module):
def __init__(self, in_features):
super(MyModel, self).__init__()
self.linear = torch.nn.Linear(in_features, 1)
self.sigmoid = torch.nn.Sigmoid()
def forward(self, x):
pred = self.linear(x)
out = self.sigmoid(pred) # 可以直接用pytorch中的sigmoid
return out
if __name__ == "__main__":
df = pd.read_csv("./data/breast_cancer.csv")
print("df", df)
print("df.columns=", df.columns)
print("df.index=", df.index)
X = df[df.columns[0:-1]].values
Y = df[df.columns[-1]].values
print("X=", X)
print("Y=", Y)
print("X.shape=", X.shape)
print("Y.shape=", Y.shape)
# 拆分数据集
x_train, x_test, y_train, y_test = train_test_split(X, Y, test_size=0.2, random_state=1111)
print("X_train={},X_train shape={}".format(x_train, x_train.shape))
print("Y_train={},Y_train shape={}".format(y_train, y_train.shape))
# 数据集的标准化,把数据缩放到某个区间之内
sc = StandardScaler()
x_train = sc.fit_transform(x_train)
x_test = sc.fit_transform(x_test)
# 转成float32类型
x_train = torch.from_numpy(x_train.astype(np.float32))
x_test = torch.from_numpy(x_test.astype(np.float32))
y_train = torch.from_numpy(y_train.astype(np.float32))
y_test = torch.from_numpy(y_test.astype(np.float32))
y_train = y_train.view(-1, 1)
print("Y_train.shape=", y_train.shape)
y_test = y_test.view(-1, 1)
print("Y_test.shape=", y_test.shape)
_, n_features = x_train.shape
print("n_features=", n_features)
model = MyModel(n_features)
print(list(model.parameters()))
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
print(list(model.parameters()))
lossF = torch.nn.BCELoss()
for epoch in range(1000):
pred = model(x_train)
loss = lossF(pred, y_train)
loss.backward()
optimizer.step()
optimizer.zero_grad()
if epoch % 5 == 0:
print(f"epoch:{epoch},loss={loss.item():.8f}")
# 画出损失函数的变化趋势
plot_x = np.arange(len(losses))
plot_y = np.array(losses)
plt.plot(plot_x, plot_y)
plt.show()
epoch:0,loss=0.95515692
epoch:5,loss=0.81455648
epoch:10,loss=0.70612687
epoch:15,loss=0.62338787
....
epoch:975,loss=0.11159274
epoch:980,loss=0.11142886
epoch:985,loss=0.11126631
epoch:990,loss=0.11110501
epoch:995,loss=0.11094501
2.3 模型测试
# 计算正确率
with torch.no_grad():
y_pred = model(x_test)
y_pred_cls = y_pred.round()
acc = y_pred_cls.eq(y_test).sum().numpy() / y_test.shape[0]
print(acc)
0.9912280701754386
正确率为99.1%,已经很高了。
3 LogisticRegression模型
其中上面的线性模型,已经在sklearn中已经封装了,我们可以直接使用LogisticRegression,直接可以得到0.95的正确率。
X = df[cancer_data.feature_names].values
y = df['target'].values
model = LogisticRegression(solver='liblinear')
model.fit(X, y)
x_0 = model.predict([X[0]])
x_98 = model.predict([X[98]])
print("对于特征X[0],预测值Y={},真实值Y={}".format(x_0[0], y[0]))
print("对于特征X[98],预测值Y={},真实值Y={}".format(x_98[0], y[98]))
print("准确率=", model.score(X, y))