深度学习第五篇---Pytorch 逻辑回归模型-乳腺癌预测

上篇波士顿房价预测使用了线性回归模型,适用于连续型目标变量的回归问题,其取值范围(-∞,+∞)。逻辑回归模型(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))

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容