整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

07-文件上传到阿里云OSS实战(二)

07-文件上传到阿里云OSS实战(二)

、创建Bucket

Bucket有点像电脑里面的盘符或者目录,我们文件的上传,必须指定上传到哪个Bucket里面。因此,在上传之前必须创建它。

在阿里云控制台点击OSS服务,然后点击【Bucket列表】就可以看到如下界面:

然后点击【创建Bucket】按钮,按下图填写:

Bucket名称:需要自己要进行命名,相当于自己文件的存储目录。

区域:这个选择很重要,因为后面调用api接口上传文件的时候需要使用。

Endpoint:这个也很重要,因为后面调用api接口上传文件的时候也需要使用。

上述选择完成后,就可以点击【确定】按钮进行创建了。

创建成功后,进入Bucket信息页面,可以点击左侧的【文件管理】按钮,出现如下界面:

好了,到此为止,阿里云的OSS配置就算完成了,新手对于这个过程感觉十分困惑,因此我这里说的比较详细。

2、OSS流式上传

下面是官网文档地址以及官方代码,我摘抄了几个入门级的代码。

https://help.aliyun.com/document_detail/84781.html?spm=a2c4g.11186623.6.787.101245dcJzaFge

(1)上传字符串

// Endpoint以杭州为例,其它Region请按实际情况填写。
String endpoint="http://oss-cn-hangzhou.aliyuncs.com";
// 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维,请登录 https://ram.console.aliyun.com 创建RAM账号。
String accessKeyId="<yourAccessKeyId>";
String accessKeySecret="<yourAccessKeySecret>";

// 创建OSSClient实例。
OSS ossClient=new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

// 创建PutObjectRequest对象。
String content="Hello OSS";
// <yourObjectName>表示上传文件到OSS时需要指定包含文件后缀在内的完整路径,例如abc/efg/123.jpg。
PutObjectRequest putObjectRequest=new PutObjectRequest("<yourBucketName>", "<yourObjectName>", new ByteArrayInputStream(content.getBytes()));

// 如果需要上传时设置存储类型与访问权限,请参考以下示例代码。
// ObjectMetadata metadata=new ObjectMetadata();
// metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
// metadata.setObjectAcl(CannedAccessControlList.Private);
// putObjectRequest.setMetadata(metadata);

// 上传字符串。
ossClient.putObject(putObjectRequest);

// 关闭OSSClient。
ossClient.shutdown();                   

(2)上传Byte数组

// Endpoint以杭州为例,其它Region请按实际情况填写。
String endpoint="http://oss-cn-hangzhou.aliyuncs.com";
// 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维,请登录 https://ram.console.aliyun.com 创建RAM账号。
String accessKeyId="<yourAccessKeyId>";
String accessKeySecret="<yourAccessKeySecret>";

// 创建OSSClient实例。
OSS ossClient=new OSSClientBuilder().build(endpoint, accessKeyId,accessKeySecret);

// 上传Byte数组。
byte[] content="Hello OSS".getBytes();
ossClient.putObject("<yourBucketName>", "<yourObjectName>", new ByteArrayInputStream(content));

// 关闭OSSClient。
ossClient.shutdown();

(3)上传网络流

// Endpoint以杭州为例,其它Region请按实际情况填写。
String endpoint="http://oss-cn-hangzhou.aliyuncs.com";
// 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维,请登录 https://ram.console.aliyun.com 创建RAM账号。
String accessKeyId="<yourAccessKeyId>";
String accessKeySecret="<yourAccessKeySecret>";

// 创建OSSClient实例。
OSS ossClient=new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

// 上传网络流。
InputStream inputStream=new URL("https://www.aliyun.com/").openStream();
ossClient.putObject("<yourBucketName>", "<yourObjectName>", inputStream);

// 关闭OSSClient。
ossClient.shutdown();

(4)上传文件流

// Endpoint以杭州为例,其它Region请按实际情况填写。
String endpoint="http://oss-cn-hangzhou.aliyuncs.com";
// 云账号AccessKey有所有API访问权限,建议遵循阿里云安全最佳实践,创建并使用RAM子账号进行API访问或日常运维,请登录 https://ram.console.aliyun.com 创建。
String accessKeyId="<yourAccessKeyId>";
String accessKeySecret="<yourAccessKeySecret>";

// 创建OSSClient实例。
OSS ossClient=new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

// 上传文件流。
InputStream inputStream=new FileInputStream("<yourlocalFile>");
ossClient.putObject("<yourBucketName>", "<yourObjectName>", inputStream);

// 关闭OSSClient。
ossClient.shutdown();

3、简单文件上传

// Endpoint以杭州为例,其它Region请按实际情况填写。
String endpoint="http://oss-cn-hangzhou.aliyuncs.com";
// 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维,请登录 https://ram.console.aliyun.com 创建RAM账号。
String accessKeyId="<yourAccessKeyId>";
String accessKeySecret="<yourAccessKeySecret>";

// 创建OSSClient实例。
OSS ossClient=new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

// 创建PutObjectRequest对象。
PutObjectRequest putObjectRequest=new PutObjectRequest("<yourBucketName>", "<yourObjectName>", new File("<yourLocalFile>"));

// 如果需要上传时设置存储类型与访问权限,请参考以下示例代码。
// ObjectMetadata metadata=new ObjectMetadata();
// metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
// metadata.setObjectAcl(CannedAccessControlList.Private);
// putObjectRequest.setMetadata(metadata);

// 上传文件。
ossClient.putObject(putObjectRequest);

// 关闭OSSClient。
ossClient.shutdown();            

4、通过自定义WEB接口上传文件

首先我们来定一个阿里云OSS文件上传的一个工具类,具体代码如下:

package com.shenmazong.utils;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.PutObjectResult;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.UUID;

public class UploadFileUtils {

    /**
     * TODO uploadAliyunOss 阿里云OSS文件上传
     * @param file
     * @param fileName
     * @return
     */
    public static String uploadAliyunOss(MultipartFile file, String fileName) {
        String url=null;
        // Endpoint以杭州为例,其它Region请按实际情况填写。
        String endpoint="http://oss-cn-beijing.aliyuncs.com";
        // 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维,请登录 https://ram.console.aliyun.com 创建RAM账号。

        // shenmazong@1642590691341298.onaliyun.com
        String accessKeyId="XXXX";
        String accessKeySecret="XXXX";

        // 创建OSSClient实例。
        OSS ossClient=new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        // 创建PutObjectRequest对象。
        // 上传文件流。
        PutObjectResult result=null;
        String newName=UUID.randomUUID().toString();
        try {
            result=ossClient.putObject("shenmazong", fileName, file.getInputStream());
            url="https://shenmazong.oss-cn-beijing.aliyuncs.com/" + fileName;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }

        // 关闭OSSClient。
        ossClient.shutdown();
        return url;
    }
}

上面的工具类,是传输web上传的文件流,然后返回能够访问的url,这样我们web网上实现图片文件上传,就很容易了。

下面我们就可以编写service来实现我们的文件上传接口了。

    /**
     * TODO 实现用户头像的上传
     * @param userId
     * @param file
     * @return
     */
    @Override
    public ResponseResult uploadHead(Integer userId, MultipartFile file) {
        ResponseResult result=ResponseResult.SUCCESS();

        //--1 验证用户是否存在
        TbUser user=iTbUserMapper.selectById(userId);
        if(user==null) {
            result.setCode(-1);
            result.setMessage("获取用户信息失败");
            return result;
        }

        //--2 上传文件
        if(file.isEmpty()){
            log.info("上传文件为空");
            result.setCode(-1);
            result.setMessage("上传文件为空");
            return result;
        }
        String fileName=file.getOriginalFilename();
        int size=(int) file.getSize();
        log.info(fileName + "-->" + size);

        UUID uuid=UUID.randomUUID();
        String suffix=fileName.substring(fileName.lastIndexOf(".")).toLowerCase();
        String newName=uuid.toString() + suffix;
        String headUrl=UploadFileUtils.uploadAliyunOss(file, newName);
        if(headUrl==null) {
            result.setCode(-1);
            result.setMessage("上传文件到OSS失败");
            return result;
        }

        //--3 修改用户头像链接
        user.setHeadurl(headUrl);
        iTbUserMapper.updateById(user);

        return result;
    }

service写好以后,就需要编写controller了,具体代码如下:

    /**
     * TODO uploadHead 上传头像图片
     * @param userId
     * @param file
     * @return
     */
    @ApiOperation(value="头像上传", notes="用户上传更新自己的头像")
    @ApiImplicitParams(value={
            @ApiImplicitParam(name="userId", value="用户ID", required=true, dataType="int", example="0")
    })
    @PostMapping(value="/uploadHead", headers="content-type=multipart/form-data")
    public ResponseResult uploadHead(@RequestParam("userId") Integer userId,
                             @RequestParam("uploadFile") MultipartFile file) {
        return userService.uploadHead(userId, file);
    }

最后是html页面的示例代码,也一并贴上来:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title></title>
    <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

    <!-- 可选的 Bootstrap 主题文件(一般不用引入) -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">

    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js" type="text/javascript" charset="utf-8"></script>
    <!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</head>
<body>
<div class="container">
    <div class="row">
        <form action="upload" method="post" enctype="multipart/form-data">
            <div class="form-group">
                <label for="uploadFile">选择文件</label>
                <input type="file" id="uploadFile" name="uploadFile">
                <p class="help-block">Example block-level help text here.</p>
            </div>
            <button type="submit" class="btn btn-primary">上传</button>
        </form>
    </div>
</div>
</body>
</html>

最后的实例演示,是我在一个演示项目中拷贝过来的代码,没有针对咱们这个教程进行整理,但是作为参考资料,这部分代码是合格的。

018年初,一个物理专业的学生Jan B?hmer创建了一个网站,用来跟踪和记录用户的点击、鼠标移动、浏览器类型和操作系统等数据。虽然用户跟踪并不新鲜,但他的方法不需要JavaScript、插件或外部库。实际上,它只使用了普通HTML文本和一点CSS。

它是如何工作的

B?hmer的概念利用了CSS的两个特性:将内容注入HTML元素的能力,以及在用户执行操作后更改样式的能力。网站的工作方式是在执行操作时使用content属性设置URL。此URL调用一个脚本,该脚本记录有关操作的详细信息,这些操作作为URL参数进行传递。使用::before和::after CSS选择器设置这个URL可以确保只在执行操作时调用URL,而不是在页面首次加载时调用URL。

例如,下面的CSS在每次单击#link元素时调用一次URL:

跟踪脚本包含记录事件和操作执行次数的代码。它还可以用于提取用户的IP地址、用户代理和其他标识信息。

下面是这样一个脚本在PHP中的示例:

检测浏览器类型

用户可以欺骗浏览器的用户代理,但是 B?hmer绕过了这个问题,他使用@supports at-rule(at-rule 是CSS样式声明,以@开头,紧跟着是标识符(charset),最后以分号(;)结尾。)测试浏览器特定的CSS属性。例如,下面的操作通过检测--webkit-appearance是可用的,而-ms-ime-align是不可用的来检测Chrome浏览器:

检测操作系统

B?hmer甚至使用字体检测来识别用户的操作系统。例如,通过检测浏览器是否支持Calibri字体家族,我们可以假定浏览器运行在Windows中:

B?hmer关于此概念的验证可以识别其他数据点,包括浏览器窗口的大小和方向、用户是否单击了链接以及用户在一个元素上停留的时间。

这种攻击在浏览器中非常难以预防。完全防止它的唯一方法就是禁用CSS,这会使网站无法使用。然而,通过使用内容安全策略(CSP),可以减少攻击者利用此漏洞的机会。

使用内容安全策略减少CSS泄漏

CSP是一组规则,它决定浏览器可以执行哪些操作,不能执行哪些操作。CSP通常用于防止跨站脚本攻击(XSS)和由浏览器加载不信任脚本导致的其他攻击。虽然CSP通常用于JavaScript文件,但它也可以应用于CSS样式和样式表。

考虑一个使用第三方提供商托管的样式表的网站。攻击者破坏样式表并将用户跟踪代码添加到页面上的链接:

当用户点击该链接时,他们的浏览器调用evil.com上托管的跟踪脚本。由于这完全是通过浏览器完成的,网站所有者完全不知道这个漏洞。

Content-Security-Policy通过设置允许哪些样式以及样式来源等规则来防止这种情况。

禁用内联样式

禁用内联样式是CSP提供的最大安全好处之一。内联样式是直接在HTML文档中声明的样式(或使用JavaScript设置的样式),而不是从样式表加载的样式。内联样式——尤其是动态生成的样式或用户创建的样式——非常难以保护。这就是为什么CSP通常会锁定所有内联脚本和样式,并将那些已被特别批准的内联脚本和样式列入白名单。

以下规则将阻止所有内联样式以及外部托管的样式表:

使用Hash和Nonce验证样式

如果阻塞内联样式是不可行的,你仍然可以使用hash和nonce来确保CSS的完整性。

Hash是由一个文件或字符串的内容生成的单向字符串。在样式表或内联样式上执行哈希函数时,除非样式发生改变,否则它总是返回相同的结果。这对于将某些内联样式和样式表加入白名单是很有用的,只需要同时验证样式没有被修改或篡改。

Nonce的功能与hash类似。使用nonce,将为每个请求生成一个新的随机数,这使得攻击者更难猜测它的值。这避免了hash的一个关键缺点,即多个输入可能生成相同的hash值(称为冲突)。

验证外部托管的样式表

样式表通常托管在第三方服务器上,如内容交付网络(content delivery networks, CDNs),但这带来了新的攻击方向。如果CDN受到威胁,如何阻止攻击者用自己修改过的版本替换样式表?子资源完整性,也叫SRI,试图解决这个问题。

SRI使用hash值来验证脚本和样式表的内容。计算每个文件的hash值,并将其附加到HTML元素的integrity属性中。当浏览器下载脚本或样式表时,计算其hash值并将其与存储在属性中的值进行比较。如果匹配,浏览器将加载脚本或样式。

这是在假设web页面是从受信任的源(如源服务器)提交的情况下运行的,而当资源是从不受信任的源(如第三方)提交的时候,就无法正常运行。如果web页面和资源都由第三方托管,攻击者只需要简单地修改web页面来匹配其CSS替换文件的hash值即可。

结论

虽然通过CSS跟踪用户的能力并不新鲜,但它确实要求我们以不同的方式考虑网页上的隐私和安全性。CSS是现代网页的基本语言之一,禁用网站的CSS将使网页的大部分内容无法使用。内容安全策略是阻止XSS攻击和CSS泄漏的最佳方法。Templarbit创建了一个“灵活的内容-安全-策略工作流”,以便于维护CSP头文件。如果你的团队正在努力为你的应用程序推出CSP,请立即注册一个免费试用版,并学习更多关于Templarbit如何解决CSS泄露的方法.

你可以在GitHub上找到B?hmer的概念验证的源代码。

相关连接:

网站——http://crookedss.bplaced.net/

最大的安全好处——https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src#Unsafe_inline_styles

通常锁定所有内联脚本——https://developers.google.com/web/fundamentals/security/csp/

加入白名单——https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src

并不新鲜——https://www.smashingmagazine.com/2014/10/css-only-solution-for-ui-tracking/

现在就注册获取免费版——https://www.templarbit.com/signup

GitHub——https://github.com/jbtronics/CrookedStyleSheets

相关知识:

SRI,Subresource Integrity 的缩写,中文:子资源完整性,由 Web 应用安全工作组(Web Application Security Working Group)发布。

英文原文:https://www.templarbit.com/blog/2018/03/20/tracking-users-with-css/
译者:忧郁的红秋裤

互联网上有很多资源可以找到关于机器学习数据集的见解和训练模型,但是关于如何使用这些模型构建实际应用程序的文章很少。

因此,今天我们将通过首先使用hackathon中的数据集来训练视频游戏销售预测模型,然后使用经过训练的模型来创建一个基本应用程序,根据用户输入为我们提供销售预测来学习此过程。

本文分为多个部分,你可以一个接一个地学习,而不必一口气完成它。从我第一次选择数据集以来,我花了整整一周的时间来完成应用程序。因此,请花时间专注于学习构建应用程序的各个方面,而不是只注意最终产品。

第1部分:生成模型

我们将使用在Machine Hack网站上运行的视频游戏销售预测hackathon中的数据集。首先,在MachineHack上创建一个帐户,然后在此链接上注册hackathon。

  • https://www.machinehack.com/hackathons/video_game_sales_prediction_weekend_hackathon_10

注册后,转到数据标签并下载zip文件,该文件将包含三个文件,即训练,测试和样品提交。

下一步将在Google Colab Notebook中介绍,你可以从以下链接打开和克隆该Notebook:

  • https://colab.research.google.com/drive/1tc9I7bxLJWCAEnqyPVi1Y2nIRLz3hNxR?usp=sharing

或者如果你想在本地或其他平台上下载并运行该Notebook,请从以下GitHub链接下载该Notebook:Jupyter Notebook链接

  • https://github.com/codeclassifiers/video-game-sales-prediction-jupyter-notebook/blob/master/Video_Game_Sales_prediction.ipynb

Notebook电脑的第一部分简要介绍了问题陈述。通过运行下面显示的下一个代码单元,上传我们收到的文件

from google.colab import files
uploaded=files.upload()
for fn in uploaded.keys():
  print('User uploaded file "{name}" with length {length} bytes'.format(
      name=fn, length=len(uploaded[fn])))

在下一个代码单元中,我们导入所需的python包。它们中的大多数已预先安装在Google Colab中,因此无需安装它们中的任何一个。

因为我们无法在hackathon结束后提交测试数据进行评估,因此本文其余部分仅将数据用于Train.csv。请记住,Train.csv的行数少于通常用于正确训练模型的行数。但是,出于学习目的,我们可以使用行数较少的数据集。

现在让我们深入研究解决此机器学习问题…

步骤1识别目标和独立特征

首先,让我们将Train.csv导入pandas数据框中,然后运行df.head()以查看数据集中的列。

从数据框中,我们可以看到目标列是SalesInMillions,其余列是独立特征

步骤2清理资料集

首先,我们通过运行input.isnull().sum()命令检查null值。

input.isnull().sum()
#Output:
ID                 0
CONSOLE            0
YEAR               0
CATEGORY           0
PUBLISHER          0
RATING             0
CRITICS_POINTS     0
USER_POINTS        0
SalesInMillions    0
dtype: int64

我们可以看到数据集中没有空值。接下来,我们可以通过运行以下命令删除不必要的列ID,因为它在目标销售中不起作用:input=input.drop(columns=['ID'])

接下来,我们可以使用train_test_split命令将数据框分为训练和测试数据集:

train, test=train_test_split(input, test_size=0.2, random_state=42, shuffle=True)

步骤3探索性数据分析

描述性统计信息

使用df.shape命令我们可以找到数据集中的总行数,并且可以使用命令df.nunique()在每个列中查找唯一值。

CONSOLE              17
YEAR                 23
CATEGORY             12
PUBLISHER           184
RATING                6
CRITICS_POINTS     1499
USER_POINTS        1877
SalesInMillions    2804

在EDA部分中,我们使用pandas profiling和matplotlib包生成各种列的图形,并观察它们与目标列的关系。

从EDA获得的一些见解是:

PS3平台的销售额最高。之后是Xbox360:

动作类别的销售额最高,难题类别的销售额最低

2007年到2011年的销售额最高:

通常,我们在EDA之后进行特征工程或特征选择步骤。但是,我们的功能较少,着重于实际使用模型。因此,我们正在朝着下一步迈进。但是,请记住,USER_POINTS和CRITICS_POINTS列可用于派生其他功能。

步骤4建立模型

由于我们具有许多分类功能,因此我们将对数据集使用catboost回归模型。由于catboost可以直接作用于分类特征,因此跳过了对分类特征进行标签编码的步骤。

首先,我们使用pip install命令安装catboost软件包。

然后,我们创建一个分类特征列表,将其传递给模型,然后将模型拟合到训练数据集上:

import catboost as cat
cat_feat=['CONSOLE','CATEGORY', 'PUBLISHER', 'RATING']
features=list(set(train.columns)-set(['SalesInMillions']))
target='SalesInMillions'
model=cat.CatBoostRegressor(random_state=100,cat_features=cat_feat,verbose=0)
model.fit(train[features],train[target])

步骤5检查模型的准确性

首先,我们根据测试数据集创建真实的预测:

y_true=pd.DataFrame(data=test[target], columns=['SalesInMillions'])
test_temp=test.drop(columns=[target])

接下来,我们在测试数据集上运行训练良好的模型以获取模型预测并检查模型准确性

y_pred=model.predict(test_temp[features])
from sklearn.metrics import mean_squared_error
from math import sqrt

rmse=sqrt(mean_squared_error(y_true, y_pred))
print(rmse)
#Output: 1.5555409360901584

我们的RMSE值为1.5,这相当不错。有关在出现回归问题时准确性指标的更多信息,可以参考本文。

  • https://medium.com/analytics-vidhya/regression-and-performance-metrics-accuracy-precision-rmse-and-what-not-223348cfcafe

如果你想进一步改善模型或尝试组合各种模型,可以在本文中参考本次hackathon获胜者的方法:

  • https://analyticsindiamag.com/meet-the-machinehack-champions-who-cracked-the-video-game-sales-prediction-hackathon/

步骤6将模型保存到pickle文件中

现在,我们可以将模型保存到pickle文件中,然后将其保存在本地:

import pickle
filename='finalized_model.sav'
pickle.dump(model, open(filename, 'wb'))

保存pickle文件后,你可以从Google Colab Notebook文件部分的左侧边栏中下载并保存在本地

额外提示

  • 添加更多数据

我们可以通过向模型添加更多数据来改善模型预测。我们可以使用一些在Kaggle上的相关的数据集。Kaggle:https://www.kaggle.com/gregorut/videogamesales

  • 提高模型效率

我们可以使用组合模型的堆栈来进一步提高模型效率。

如果你已完成此步骤,请轻拍一下自己的背,因为我们刚刚完成了项目的第一个主要部分。休息一会儿,拉伸一下,然后开始本文的下一部分。

第2部分:根据模型创建后端API

我们将使用Python Flask创建后端API。

因此,首先在本地创建一个名为server的文件夹。另外,如果还没有,请在你的计算机上安装Python和pip软件包管理器。

接下来,我们需要在文件夹中创建一个虚拟环境。我在Linux上使用python3,因此我创建虚拟环境的命令为:python3 -m venv server。

你可以在本文中查看适用于你的OS和Python版本的命令:Python venv:https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/

接下来,我们将通过运行以下命令激活虚拟环境: source server/bin/activate

完成后,我们需要安装Flask pip软件包: pip install -U Flask

接下来,使用你喜欢的文本编辑器在服务器文件夹中创建一个名为“app.py”的文件,并添加以下代码以创建基本的API:

from flask import Flask, jsonify, make_response, request, abort
app=Flask(__name__)
@app.route("/")
def hello():
  return "Hello World!"
if __name__=="__main__":
  app.run()

现在打开终端并运行python3 app.py以启动服务器。这将主要在5000端口上启动服务器。为了测试API,请在浏览器中打开此链接:http://localhost:5000/

你应该在浏览器中打印出Hello World。如果不是,则在启动API时检查API是否在其他端口上运行或在终端上打印错误。

我们将调用POST API,因此最好在继续进行之前安装Postman工具。使用此工具将向服务器发送POST请求。

接下来,我们需要使用以下命令安装catboost,pandas和Flask-Cors pip软件包:

pip install catboost pandas Flask-Cors

接下来,将我们在第1部分末尾下载的经过训练的模型的pickle文件(finalized_model.sav)复制到服务器文件夹中。

现在,使用以下代码更新 app.py:

from flask import Flask, jsonify, make_response, request, abort
import pandas as pd
import catboost
import pickle
from flask_cors import CORS,cross_origin
model=pickle.load(open( "finalized_model.sav", "rb"))
app=Flask(__name__)
app.config['CORS_HEADERS']='Content-Type'
cors=CORS(app)
@app.errorhandler(404)

def not_found(error):
    return make_response(jsonify({'error': 'Not found'}), 404)

@app.route("/")
def hello():
  return "Hello World!"

@app.route("/get_prediction", methods=['POST','OPTIONS'])
@cross_origin()
def get_prediction():
    if not request.json:
        abort(400)
    df=pd.DataFrame(request.json, index=[0])
    cols=["CONSOLE","RATING","CRITICS_POINTS","CATEGORY","YEAR","PUBLISHER","USER_POINTS"]
    df=df[cols]
    return jsonify({'result': model.predict(df)[0]}), 201

if __name__=="__main__":
  app.run()

在第6行中,我们将训练后的模型导入到我们的python文件中。

在第10行,我们初始化CORS模块以允许来自客户端API调用的请求。

在第11行,我们定义了一个错误处理程序,如果从服务器访问了任何未处理的异常或未定义的路径,它将发送错误响应。

对我们来说,主要的兴趣点是从第19行定义的get_prediction POST API。get_prediction方法是我们从客户端获取数据并提供响应的销售预测。

在第24行,我们将来自API请求的数据转换为pandas数据框。现在,我们的模型期望列以特定顺序提供正确的响应。因此,在第25行中,我们指定列顺序。在接下来的步骤中,以所需顺序重新排列列。

在第27行,model.predict用于从模型中获取预测,并将其作为响应传递给客户端。在这一步,我们准备好在本地使用该API。我们可以通过发送POST-API调用来测试Postman客户端中的API,如截图所示:

你可以在上述请求的正文部分中添加一个JSON示例,可以在代码Github-gist中找到。

  • https://gist.github.com/codeclassifiers/67ea0145b1626984af6619e8a301c067

确保在主体和主体类型中选择raw和JSON选项(如屏幕截图所示),并在请求类型中选择POST。

如果在此步骤之前一切正常,那么恭喜你,你现在有了一个后端API,该API可根据输入参数根据经过训练的模型进行预测。

额外提示

  • 模块化代码

在后端设计中不建议在单个文件中编写API,我们可以将路径和模型导入分隔到不同的文件夹中,以使代码更具模块化。如果将来引入其他API,这也将使我们能够以可管理的方式扩展代码。

在这一点上,我们可以再次休息一下,确保收藏了本文,以便轻松地重新开始本项目的下一部分。

第3部分:将后端API部署到Heroku

到目前为止,我们的API在本地工作,但我们需要将其部署在远程服务器上,以供其他地方使用。为此,我们将使用Heroku作为我们的API托管平台。

我主要参考了来自stackabuse 的文章,将其部署到Heroku。我们将简要介绍这些步骤,但是如果你卡在了其中某个步骤,请在此处参阅原始文章:

  • Stackabuse文章:https://stackabuse.com/deploying-a-flask-application-to-heroku/

首先,我们使用terminal命令安装gunicorn:

pip install gunicorn

接下来,运行下面的命令将所有已安装的pip包,存储到require.txt文件:

pip freeze > requirements.txt

你可以参考此处上传的requirements.txt文件以供参考:

  • Github链接:https://github.com/codeclassifiers/video-game-sales-prediction-backend/blob/master/requirements.txt

接下来,Procfile使用以下代码在服务器文件夹中创建一个名称为Procfile的文件:

web: gunicorn app:app

现在,在Heroku网站上注册,在该网站上创建一个应用,然后按照原始文章中的说明安装Heroku CLI 。

接下来,通过运行以下命令从本地终端登录Heroku: heroku login -i

使用以下命令添加你的Heroku应用git参考:

heroku git:remote -a {your-project-name}

现在,使用以下命令将代码推送到Heroku:

git push heroku master

在运行上述命令的最后,你将在终端输出中获得API URL,现在我们可以使用该URL从客户端进行调用。此时,我们还可以从PostMan应用程序发送API请求,以查看我们是否正确收到了与步骤2末尾所述类似的响应。

到目前为止的代码库可以在以下Github存储库中找到

  • Videogame Sales Backend:https://github.com/codeclassifiers/video-game-sales-prediction-backend

现在,我们在服务器上托管了一个正常工作的API。如果一切正常,那么我们可以继续开发客户端应用程序。如果遇到任何问题,请在评论部分中提及你的问题。

第4部分:使用react和bootstrap创建客户端应用程序

我们将需要在我们的计算机上正确安装和设置Node.js。因此,请先下载并安装适用于你相关操作系统和系统的Node.js,然后再继续进行操作。另外,建议安装yarn管理器:yarn 安装

现在,在上一步中创建frontend的服务器文件夹外部创建一个新文件夹,并从终端进入该frontend文件夹内部。

接下来,我们将创建一个新的react应用程序,并通过在终端中运行以下命令来启动它:

npx create-react-app sales-prediction-app
cd sales-prediction-app
npm start

你应该在浏览器中看到默认打开的浏览器选项卡,以及react.js默认模板应用程序。现在我们需要在我们最喜欢的编辑器中打开该项目(我正在使用VSCode),并开始进行更改以构建前端应用程序。

首先,我们需要在应用程序公共文件夹中的index.html文件中导入相关的引导程序文件。

我们需要按照bootstrap文档提供的说明在index.html文件中添加文件,如下所示:

  • bootstrap文档:https://getbootstrap.com/docs/4.5/getting-started/introduction/
<head>
...
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
...
</head>
<body>
...
<div id="root"></div>
...
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
...
</body>

我们的最终用户界面是集合下拉菜单项,其中单个项如下所示:

我们将在src文件夹中创建一个名称为optionsSources.json的JSON文件。JSON文件中的每个条目都包含以下对象:

{
    "CONSOLE": {
        "options": [
            "ps2","x360","ps3","pc"
        ],
        "icon": "?",
        "dropDownPlaceholder": "Select Console"
    }
}

下拉菜单中显示的选项位于options数组中,下拉菜单选项左侧显示的图标和标签位于icon和dropDownPlaceholder项。我们需要创建多个这样的下拉列表,因此要添加的完整JSON文件如以下文件所示:

  • https://github.com/codeclassifiers/video-salesprediction-frontend/blob/master/src/optionsSources.json

接下来,我们需要在我们的应用程序中实现下拉组件。在src文件夹中创建一个名为components的文件夹,并在components文件夹中创建一个名为OptionSelection.js的文件

我们将编写一个功能组件,该组件返回一个下拉项,如下所示:

import React,{ useState } from 'react';
import optionSources from  '../optionsSources.json';
function OptionSelection({itemKey, setOptionInObject}) {
    const title=optionSources[itemKey].dropDownPlaceholder;
    const icon=optionSources[itemKey].icon;
    return(
        <div className="d-flex justify-content-start align-items-center mt-2 selection-item">
            <div className="option-label">
            <b><span role="img" aria-label="label-icon">{icon}</span>{` ${title}`}</b>
            </div>
            <div className="dropdown ml-4">
            <button className="btn btn-primary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                {title}
            </button>
            <div className="dropdown-menu" aria-labelledby="dropdownMenuButton">
                {renderOptionsDropdown()}
            </div>
            </div>
        </div>
    )
}

export default OptionSelection;

在上面的组件中,我们itemKey从第3行的父组件中获得prop(param)值。我们假设itemKey从父组件接收的是CONSOLE。在第4行和第5行,我们首先提取显示在下拉菜单左侧的标题和图标。然后,根据Boostrap文档在创建下拉列表时,在第6行的返回函数中使用HTML标签。

接下来,我们需要实现renderOptionsDrop在返回函数中定义的函数,如下所示:

import optionSources from  '../optionsSources.json';
function OptionSelection({itemKey, setOptionInObject}) {
...
const renderOptionsDropdown=()=> {
        const selectionOptions=optionSources[itemKey].options;
        return selectionOptions.map((selectionOption, index)=>{
            return (
                <div className="dropdown-item pointer" 
                     key={`${index}${selectionOption}`} 
                     onClick={()=> handleDropDownSelection(selectionOption)}
                >
                    {selectionOption}
                </div>
            );
        })
}
...
}

在第5行,我们从optionSources JSON对象获取特定项的options数组,并将其存储在selectionOptions变量中。

然后在第6行,我们使用map函数迭代数组并显示下拉选择项。我们必须在第10行使用onClick函数更新下拉项的选定值。

然后实现onClick处理程序中的函数handleDropDownSelection,如下所示:

import React,{ useState } from 'react';
...
function OptionSelection({itemKey, setOptionInObject}) {
    const [currentSelectedOption, setSelectedOption]=useState(null);
    const handleDropDownSelection=(consoleOption)=> {
        setSelectedOption(consoleOption)
        setOptionInObject(itemKey, consoleOption)
    }
    ...
}

我们在第1行输入了useState hook。它是一个内部函数,允许我们使用状态变量的概念动态更新值。关于这个函数的更多信息可以在这里找到:

  • React useState docs:https://reactjs.org/docs/hooks-state.html

在第7行,我们更新下拉列表的选定选项。在第8行中,我们将选择的值传递回父函数以进行进一步处理。

这个组件的完整代码可以在这里找到:https://github.com/codeclassifiers/video-salesprediction-frontend/blob/master/src/components/ConsoleSelection.js

然后我们在src文件夹中导入此选项并对服务器进行API调用。完整的代码可以在这里找到:

  • https://github.com/codeclassifiers/video-salesprediction-frontend/blob/master/src/App.js

然后在handleInputSubmission函数中对后端进行API调用,如下所示:

import React, {useState} from 'react';
import axios from 'axios';
function App() {
  ...
  const handleInputSubmission=()=> {
    if(selectedObject && Object.keys(selectedObject).length===7) {
      ...
      axios.post(process.env.REACT_APP_HEROKU_SERVER_URL, selectedObject)
      .then(function (response) {
        setPredictionLoading(false)
        setModelPrediction(response.data.result)
      })
      .catch(function (error) {
        setPredictionLoading(false)
        setRequestFailed("Some error ocurred while fetching prediction")
      });
    } else {
      setRequestFailed("Please select all fields before submitting request")
    }
  }
}

我们正在使用Axios npm模块对后端Heroku服务器进行POST API调用。确保在process.env.REACT_APP_HEROKU_SERVER_URL占位符的第8行上添加自己的Heroku服务器URL,以接收来自服务器API的响应。

最好将API URL变量保存在.env文件中,然后在部署环境中进行设置。可以在这里找到更多详细信息:

  • Env变量: https://create-react-app.dev/docs/adding-custom-environment-variables/

在此处找到Github上的前端应用程序的完整资源:

  • 视频游戏销售预测前端:https://github.com/codeclassifiers/video-salesprediction-frontend

这使我们有了在线部署Web应用程序的最后一步。因此,请耐心一些,让我们开始项目的最后一步。

第5部分:将客户端应用程序部署到Netlify

Netlify是一个可以轻松在线部署静态网站的平台。在部署使用createreact app模块生成的应用程序时,它有一个非常简单的过程。我们将利用此服务在线托管我们的web应用程序。

首先,我们需要在Github上创建一个帐户。

然后,我们需要将前端文件夹上传到Github存储库。我们可以按照官方文档中显示的步骤将项目部署到Github:官方文档(https://docs.github.com/en/github/importing-your-projects-to-github/adding-an-existing-project-to-github-using-the-command-line)

一旦该项目在GitHub上进行部署,通过遵循以下官方文档即可简单,直接地完成netlify的过程:Netlify Deploy(https://www.netlify.com/blog/2016/09/29/a-step-by-step-guide-deploying-on-netlify/)

如果你已在上一步中将环境变量用于服务器URL,请确保如本文档所示将其添加到netlify dashboard中。

最后,我们将提供一个如下所示的网络应用程序:

额外提示

  • 改进UI和配色方案

老实说,上面的UI非常简单。它没有很好的配色方案(主要是因为像我这样的开发人员不是优秀的设计师)。你可以改善设计并调整CSS,以更好地查看网页。

尾注

这样就完成了从机器学习hackathon数据集创建销售预测Web应用程序的过程。