分类问题属于机器学习问题的范畴,在给定一组特征的情况下,任务是预测离散值。分类问题的一些常见示例是预测肿瘤是否是癌症或学生是否有可能通过考试。在本文中,根据银行客户的一些特征,我们将预测客户是否有可能在6个月后离开银行。客户离开组织的现象也称为流失。因此,我们的任务是根据各种客户特征来预测客户流失。$pipinstallpytorchdatasets允许我们将所需的库和数据集导入我们的Python应用程序:`importtorchimporttorch.nnasnnimportnumpyasnpiportpandasaspdimportmatplotlib.pyplotaspltimportseabornassns%matplotlibinline`我们可以使用read_csv()pandas库的方法来导入包含我们的数据集的CSV文件。dataset=pd.read_csv(r'E:Datasets\customer_data.csv')让我们打印数据集:dataset.shape输出:(10000,14)输出显示数据集有10k条记录和14列。我们可以使用pandasdataframe的head()方法来打印数据集的前五行。dataset.head()输出:您可以在我们的数据集中看到14列。基于前13列,我们的任务是预测第14列的值,即Exited。探索性数据分析让我们对数据集进行一些探索性数据分析。我们将从预测6个月后实际离开银行的客户比例开始,并使用饼图将其可视化。让我们首先增加图形的默认绘图大小:`fig_size=plt.rcParams["figure.figsize"]fig_size[0]=10fig_size[1]=8plt.rcParams["figure.figsize"]=fig_size`下面的脚本绘制了Exited列的饼图。dataset.Exited.value_counts().plot(kind='pie',autopct='%1.0f%%',colors=['skyblue','orange'],explode=(0.05,0.05))输出:输出显示,在我们的数据集中,有20%的客户离开了银行。这里1表示客户已经离开银行,0表示客户没有离开银行。让我们绘制数据集中所有地理位置的客户数量:输出:输出显示几乎一半的客户来自法国,而西班牙和德国各占25%。现在,让我们绘制每个唯一位置的客户数量以及流失信息。我们可以使用seaborn库中的countplot()函数来做到这一点。输出:输出显示虽然法国客户总数是西班牙和德国客户的两倍,但法国和德国客户离开银行的比例相同。同样,德国和西班牙客户的总数相同,但离开银行的德国客户是西班牙客户的两倍,这表明德国客户更有可能在6个月后离开银行。数据预处理在训练PyTorch模型之前,我们需要对数据进行预处理。如果查看数据集,您会发现它有两种类型的列:数字列和分类列。数字列包含数字信息。CreditScore、Balance、Age等。同样,Geography和Gender是分类列,因为它们包含分类信息,例如客户的位置和性别。有几列可以被视为数字和分类。例如,HasCrCard列的值可以为1或0。但是,HasCrCard列包含有关客户是否有信用卡的信息。但是,这完全取决于数据集的领域知识。让我们再次输出数据集中的所有列,找出哪些列可以被认为是数字列,哪些列应该被认为是分类列。数据框的columns属性显示所有列名:'Balance','NumOfProducts','HasCrCard','IsActiveMember','EstimatedSalary','Exited'],dtype='object')`在我们的数据列中,我们不会使用RowNumber、CustomerId和Surname列,因为这些Column值是完全随机的,与输出无关。例如,客户的姓氏对客户是否离开银行没有影响。在其余列中,可以将Geography、Gender、HasCrCard和IsActiveMember列视为分类列。让我们创建一个包含这些列的列表:除了这一列之外的所有列都可以被认为是数字的。numerical_columns=['CreditScore','Age','Tenure','Balance','NumOfProducts','EstimatedSalary']最后,输出(Exited列中的值)存储在输出变量中。我们已经创建了分类、数字和输出列列表。但是,目前,分类列的类型不是分类的。您可以使用以下脚本检查数据集中所有列的类型:输出:`RowNumberint64CustomerIdint64SurnameobjectCreditScoreint64GeographyobjectGenderobjectAgeint64Tenureint64Balancefloat64NumOfProductsint64HasCrCardint64IsActiveMemberint64EstimatedSalaryfloat64Exitedint64dtype:object`您可以看到Geographyand?Gender列的类型是object,HasCrCardand?IsActive列的Thetypeisint64.Weneedtoconvertthetypeofthecategoricalcolumntocategory.我们可以使用astype()函数执行此操作,如下所示:现在,如果您再次绘制数据集中列的类型,您将看到以下结果:float64Exitedint64dtype:object`现在让我们看看Geography列中的所有类别:Output:Index(['France','Germany','Spain'],dtype='object')类别,列中的每个类别都分配有唯一代码。例如,让我们绘制Geography列的前五行并打印前五行的代码值:Output:`0France1Spain2France3France4SpainName:Geography,dtype:categoryCategories(3,object):[France,Germany,Spain]`下面的脚本CodeGeography的值绘制在列的前五行中:Output:`0012203042dtype:int8`输出显示France已被编码为0和Spain已经编码为2。将分类列和数值列分开的基本目的是让数值列中的值可以直接馈入神经网络。但是,分类列的值必须先转换为数字类型。分类列中值的编码部分解决了分类列的数值转换任务。由于我们将使用PyTorch进行模型训练,因此我们需要将分类列和数字列转换为张量。首先让我们将分类列转换为张量。在PyTorch中,可以从numpy数组创建张量。我们首先将四个分类列中的数据转换为numpy数组,然后将所有列水平堆叠,如以下脚本所示:`geo=dataset['Geography'].cat.codes.values...`上面该脚本打印出类别列中的前十条记录,水平堆叠。输出如下:Output:`array([[0,0,1,1],[2,0,0,1],[0,0,1,0],[0,0,0,0],[2,0,1,1],[2,1,1,0],[0,1,1,1],[1,0,1,0],[0,1,0,1],[0,1,1,1]],dtype=int8)`现在要从上面的numpy数组创建张量,只需将数组传递给torch模块的张量类。输出:`tensor([[0,0,1,1],[2,0,0,1],[0,0,1,0],[0,0,0,0],[2,0,1,1],[2,1,1,0],[0,1,1,1],[1,0,1,0],[0,1,0,1],[0,1,1,1]])`在输出中,您可以看到分类数据的numpy数组现在已转换为张量对象。同样,我们可以将数值列转换为张量:`numerical_data=np.stack([dataset[col].valuesforcolinnumerical_columns],1)...`输出:`tensor([[6.1900e+02,4.2000e+01、2.0000e+00、0.0000e+00、1.0000e+00、1.0135e+05]、[6.0800e+02、4.1000e+01、1.0000e+00、8.3808e+04、1.0000e+00,1.1254e+05],[5.0200e+02,4.2000e+01,8.0000e+00,1.5966e+05,3.0000e+00,1.1393e+05],[6.9900e+02,3.9000e+01,1.0000e+00,0.0000e+00,2.0000e+00,9.3827e+04],[8.5000e+02,4.3000e+01,2.0000e+00,1.2551e+05,1.0000e+00,7.9084e+04]])`在输出中,您可以看到前五行包含我们数据集中六个数字列的值。最后一步是将输出的numpy数组转换为张量对象。...输出:tensor([1,0,1,0,0])现在,让我们绘制分类数据、数值数据和相应输出的形状:...输出:`torch.Size([10000,4])torch.Size([10000,6])torch.Size([10000])`在训练模型之前,有一个非常重要的步骤。我们将分类列转换为数值,其中唯一值由单个整数表示。例如,在Geography列中我们看到法国用0表示,德国用1表示。我们可以使用这些值来训练我们的模型。然而,更好的方法是将分类列中的值表示为N维向量而不是单个整数。我们需要为所有分类列定义嵌入维度(向量维度)。关于维数没有硬性规定。定义列的嵌入大小的一个好的经验法则是将列中唯一值的数量除以2(但不超过50)。例如,对于Geography列,唯一值的数量为3。该Geography列的相应嵌入大小为3/2=1.5=2(四舍五入)。以下脚本创建一个元组,其中包含所有分类列的唯一值数量和维度大小:`categorical_column_sizes=[len(dataset[column].cat.categories)forcolumnincategorical_columns]...`输出:[(3,2),(2,1),(2,1),(2,1)]使用训练数据训练有监督的深度学习模型,例如我们在本文中开发的模型,并在测试数据集模型性能。因此,我们需要将数据集拆分为训练集和测试集,如下脚本所示:`total_records=10000....`我们的数据集中有10,000条记录,其中80%(即8,000条记录)将被用于训练模型,而其余20%的记录将用于评估模型的性能。请注意,在上面的脚本中,分类和数字数据和输出已拆分为训练集和测试集。验证我们是否正确地将数据分成训练集和测试集:`print(len(categorical_train_data))print(len(numerical_train_data))print(len(train_outputs))print(len(categorical_test_data))print(len(numerical_test_data))print(len(test_outputs))`输出:`800080008000200020002000`创建一个预测模型我们已经将数据分成训练集和测试集,现在是定义训练模型的时候了。为此,我们可以定义一个名为Model的类,用于训练模型。看下面的脚本:`classModel(nn.Module):def__init__(self,embedding_size,num_numerical_cols,output_size,layers,p=0.4):super().__init__()self.all_embeddings=nn.ModuleList([nn.Embedding(ni,nf)forni,nfinembedding_size])self.embedding_dropout=nn.Dropout(p)self.batch_norm_num=nn.BatchNorm1d(num_numerical_cols)...returnx`接下来,求输入的大小层,将分类和数字列的数量相加并存储在input_size变量中。之后,for循环迭代,将对应的图层添加到all_layers列表中。添加的层是:Linear:用于计算输入和权重矩阵之间的点积ReLu:用作激活函数BatchNorm1d:用于对数值列应用批量归一化Dropout:用于避免postfor循环中的过度拟合,附加输出层的层列表。由于我们希望神经网络中的所有层按顺序执行,因此将层列表传递给nn.Sequential类。接下来,在forward方法中,将分类列和数字列作为输入传递。类别列的嵌入发生在以下几行中。`embeddings=[]...`数字列的批量归一化可以通过以下脚本应用:x_numerical=self.batch_norm_num(x_numerical)最后,嵌入的分类列x和数字列x_numerical被连接起来并传递给序列层。训练模型要训练模型,首先我们必须创建上一节中定义的类模型的对象。...您可以看到我们传递了分类列的嵌入大小、数字列的数量、输出大小(在我们的例子中为2)以及隐藏层中的神经元数量。你可以看到我们有三个隐藏层,分别有200、100和50个神经元。您可以根据需要选择其他尺寸。让我们打印模型看看:print(model)Output:`Model((all_embeddings):ModuleList(...))`你可以看到在第一个线性层中,in_features变量的值为11,因为我们有6数字列,分类列的嵌入维度之和为5,因此6+5=11。out_features的值为2,因为我们只有2个可能的输出。在实际训练模型之前,我们需要定义损失函数和将用于训练模型的优化器。以下脚本定义了损失函数和优化器:`loss_function=nn.CrossEntropyLoss()...`现在我们拥有了训练模型所需的一切。以下脚本训练模型:`epochs=300aggregated_losses=[]foriinrange(epochs):...print(f'epoch:{i:3}loss:{single_loss.item():10.10f}')`neural元数设置为300,这意味着要训练模型,整个数据集将被使用300次。for是300次,每次迭代执行循环的方式,使用损失函数计算损失。每次迭代期间的损失将添加到aggregated_loss列表中。要更新权重,调用single_loss对象的backward()函数。最后,优化器函数的step()方法更新梯度。上面脚本的输出如下:`epoch:1loss:0.71847951epoch:26loss:0.57145703epoch:51loss:0.48110831epoch:76loss:0.42529839epoch:101loss:0.39972275epoch:126loss:0.37837571epoch:151loss:0.37133673epoch:176损失:0.36773482时代:201损失:0.36305946时代:226损失:0.36079505时代:251损失:0.35350436时代:276损失:0.35540250时代:300损失:0.34657个脚本epochs),aggregated_losses)plt.ylabel('Loss')plt.xlabel('epoch');`输出:输出显示损失最初迅速减少。在第250个纪元之后,损失几乎没有减少。进行预测最后一步是对测试数据进行预测。为此,我们只需要将categorical_test_data和numerical_test_data传递给模型类。然后可以将返回值与实际测试输出值进行比较。以下脚本对测试类进行预测并在测试数据上打印交叉熵损失。`withtorch.no_grad():...`Output:Loss:0.36855841测试集上的损失为0.3685,略高于训练集上获得的0.3465,这表明我们的模型略微过度拟合。由于我们指定输出层将包含2个神经元,因此每个预测将包含2个值。例如,前5个预测值看起来像这样:print(y_val[:5])输出:`tensor([[1.2045,-1.3857],[1.3911,-1.5957],[1.2781,-1.3598],[0.6261,-0.5429],[2.5430,-1.9991]])`这个预测的思路是如果实际输出为0,那么索引0处的值应该大于索引1处的值,反之亦然.我们可以使用以下脚本检索列表中最大值的索引:y_val=np.argmax(y_val,axis=1)输出:现在让我们再次打印y_val列表的前五个值:print(y_val[:5])Output:tensor([0,0,0,0,0])由于初始预测输出列表中前五个记录的零索引处的值大于第一个索引处的值,因此可以在处理后的输出之前使用,请参见五行中的0。最后,我们可以使用sklearn.metrics模块中的confusion_matrix、accuracy_score和classification_report类来查找测试集的准确度、精确度和召回值,以及混淆矩阵。`fromsklearn.metricsimportclassification_report,confusion_matrix,accuracy_scoreprint(confusion_matrix(test_outputs,y_val))print(classification_report(test_outputs,y_val))print(accuracy_score(test_outputs,y_val))`输出:`[[152783][6]22416]精确召回F1得分支持00.870.950.91161010.670.430.430.52390microAVG0.850.850.850.852000macroavg0.770.690.710.712000WAIGHTEGETEFTEM2000WAIGHIGHIGHTEDAVG0.830.830.850.850.830.830.8320000.84655.8465输出随机选择的事实是神经网络模型的所有参数,这是非常令人印象深刻的。我建议你尝试改变模型参数,比如训练/测试比率、隐藏层的数量和大小等,看看你是否能得到更好的结果。结论PyTorch是Facebook开发的常用深度学习库,可用于分类、回归和聚类等各种任务。本文介绍如何使用PyTorch库对表格数据进行分类。
