实验名称 实验1 机器学习模型评估实践 验证型
实验目的及要求:
1.掌握留出法、交叉验证法、自助法等数据集拆分方法;
2.掌握错误率、准确率、精确度、召回率、F1指标、真阳性率、假阳性率等指标的计算方法;
3.能够计算并绘制Precision-Recall(PR)曲线,并计算曲线下面积;
4.能够计算并绘制ROC曲线,并计算曲线下面积;
5.了解调用机器学习算法实现算法性能评估及预测的基本流程。
实验内容:
【实验项目1】
(1) 利用python或matlab实现“留出法” 拆分数据集;
(2) 利用python或matlab实现“交叉验证法”拆分数据集;
(3) 利用python或matlab实现“自助法”拆分数据集。
注:自行随机生成数据集(特征+标签),或调用scikit-learn内置数据集。

【实验项目2】
(1) 利用python或matlab实现“错误率”、 “准确率” 指标的计算;
(2) 利用python或matlab实现“精确度” 、“召回率” 、“F1”指标的计算, 并绘制Precision-Recall(PR)曲线,计算曲线下面积;
(3) 利用python或matlab实现“真阳性率” 、“假阳性率”指标的计算,并绘制ROC曲线,计算曲线下面积。
注:可自行生成预测分值列表和标签列表用于测试代码,或调用scikit-learn内置数据集和预测方法。

【实验项目3】
(1) 尝试利用python或matlab,采用“交叉验证”或者其它方式,评估机器学习算法的性能,了解评估流程。

参考教材资料
《机器学习》,周志华,清华大学出版社,2016年
sklearn模型选择和评估 https://www.scikitlearn.com.cn/0.21.3/30/

实验考核材料
(1)实验报告:按实验内容及任务要求,记录各项任务的关键步骤和截屏;
(2)代码脚本:保存完整的执行脚本。
将所有材料打包,提交到作业系统。

操作步骤:
这里我使用非常著名的新手数据集mnist。为了示范简单的深度学习训练(机器学习的一个非常大分支,能达到的效果和传统机器学习是一样的)、评估的完整流程,我从mnist测试集入手,不使用类似datasets.MNIST的接口,从而使得这套流程可以套用你自己的分类数据集。因为是分类任务,我直接将类别嵌入文件名,这样对图片进行处理的同时,类别标签也得到了处理。另外,本实验尽量没有使用框架,如果你是新手,弄懂这一套流程你会大受裨益!

有人反馈说运行不了,很大原因是你环境没配好,这个需要你百度、csdn自己配一下,实在配不好的可以评论区提问,我看到会回答的,其他问题也可以提,环境配好,文件目录改一下,文末的压缩包代码基本是肯定可以运行的,环境还是配置好,后续实验我应该会更新的,本次所需要的功能库如下:
必须的:
torch和torchvision可以安装gpu版本或者cpu版本,看你电脑是否有英伟达显卡

import random
import shutil
from glob import glob
import os
import numpy as np
from matplotlib import pyplot as plt
import cv2
#以下只需要安装torch就行
from torch.nn.functional import cross_entropy
import torch
from torch import nn
from torch.utils.data import DataLoader
from torch.utils.data import Dataset
#安装torchvision就行了
from torchvision.models import resnet18
import torchvision.transforms as tf

非必需:(只是可以查看进度与屏蔽一些多余的警告)

from tqdm import tqdm
import warnings
  1. 在官网下载数据集(下载链接:http://yann.lecun.com/exdb/mnist/)

  2. 然后我们需要把它转化成图片格式

def byte2img():import numpy as npimport structfrom PIL import Imageimport osdataset_path = './'  # 需要修改的路径:解压的数据集所在文件夹data_file = dataset_path + 't10k-images.idx3-ubyte'# It's 7840016B, but we should set to 7840000Bdata_file_size = 7840016data_file_size = str(data_file_size - 16) + 'B'data_buf = open(data_file, 'rb').read()magic, numImages, numRows, numColumns = struct.unpack_from('>IIII', data_buf, 0)datas = struct.unpack_from('>' + data_file_size, data_buf, struct.calcsize('>IIII'))datas = np.array(datas).astype(np.uint8).reshape(numImages, 1, numRows, numColumns)label_file = dataset_path + 't10k-labels.idx1-ubyte'# It's 10008B, but we should set to 10000Blabel_file_size = 10008label_file_size = str(label_file_size - 8) + 'B'label_buf = open(label_file, 'rb').read()magic, numLabels = struct.unpack_from('>II', label_buf, 0)labels = struct.unpack_from('>' + label_file_size, label_buf, struct.calcsize('>II'))labels = np.array(labels).astype(np.int64)test_path = dataset_path + 'mnist_test'if not os.path.exists(test_path):os.mkdir(test_path)# 新建 0~9 十个文件夹,存放转换后的图片for i in range(10):file_name = test_path + os.sep + str(i)if not os.path.exists(file_name):os.mkdir(file_name)for ii in range(numLabels):img = Image.fromarray(datas[ii, 0, 0:28, 0:28])label = labels[ii]file_name = test_path + os.sep + str(label) + os.sep + str(ii) + '_' + str(label) + '.png'#类别嵌入文件名img.save(file_name)

类别嵌入文件名:

划分前数据分布情况:

def plot(x_value,title):#画图fig=plt.figure()nums, bins, patches=plt.hist(x_value, bins=10,edgecolor="r",rwidth=0.5,align='left',range=(0,10))y_max=int(max(nums))+100plt.ylim((0,y_max))for x in range(10):plt.text(bins[x]-0.3 , nums[x]+int(y_max/30), int(nums[x]),  va='top',color='k', fontsize=8)plt.xticks(range(10))plt.title(title)plt.xlabel("class")plt.ylabel("num")plt.savefig('./'+str(y_max)+'.png')plt.close(fig)
def analyse():#分析数据分布data_dir=r'C:\Users\River\Desktop\dataset\mnist_test\*'data_distribution_lst=[]for i,class_dir in enumerate(glob(data_dir)):data_file=os.listdir(class_dir)nums=int(len(data_file))class_num=[i]*numsdata_distribution_lst+=class_numplot(data_distribution_lst)

【实验项目1】
(4) 利用python或matlab实现“留出法” 拆分数据集;
原理:留出法:直接将数据集D划分为两个互斥的集合,一个作为训练集S,另一个作为测试集 T,即 D=S∪T,S∩T=∅。
说明:
(a)测试集合和训练集合尽可能保持数据分布的一致性,比如要保证正反样本的比例不变(这是一种导致过拟合的原因);
(b)在给定了训练/测试集合的样本比例之后,仍要存在多种的划分方式,对数据集合D进行分割(单次使用留出法结果往往不够稳定);
(c)训练/测试集合的大小比例问题;
测试集合过小,会导致测评结果的方差变大;
训练集合过小,会导致偏差过大,一般使用的都是2/3~4/5的样本用于训练
按9:1划分数据分布情况:

def move_file(rate=0.9):#留出法data_dir=r'C:\Users\River\Desktop\dataset\mnist_test\*'#原数据目录train_dir=r'C:\Users\River\Desktop\dataset\mnist_test\train'#划分后训练数据目录test_dir=r'C:\Users\River\Desktop\dataset\mnist_test\test'#划分后测试数据目录for class_dir in tqdm(glob(data_dir)):#分层抽样file_name_=os.path.join(class_dir,'*.png')file_lst=glob(file_name_)random.shuffle(file_lst)#随机打乱nums=int(len(file_lst)*rate)train_lst=file_lst[:nums]test_lst=file_lst[nums:]for img_path in train_lst:shutil.copy(img_path,train_dir)for img_path in test_lst:shutil.copy(img_path,test_dir)



(5) 利用python或matlab实现“交叉验证法”拆分数据集;
原理:交叉验证法:随机将样本拆分成K个互不相交大小相同的子集,然后用K-1个子集作为训练集训练模型,用剩余的子集测试模型,对K中选择重复进行,最终选出K次测评中的平均测试误差最小的模型
说明:
(a)假设我们确定了折数k,由于进行划分的方法不同,得到的结果也会不同(例如使用不同的随机数种子)。在实际使用中,我们可以进行 p次不同的k折交叉验证,取p*k 次实验结果的平均作为最终的实验结果,这叫做p次k折交叉验证。例如,10次10折交叉验证共进行了100次实验。
(b)与留出法类似,划分为S折存在多种方式,所以为了减小样本划分不同而引入的误差,通常随机使用不同的划分重复P次,即为P次S折交叉验证

5折交叉数据分布情况(第一折交叉):

def k_fold(k):#k折交叉验证data_dir=r'C:\Users\River\Desktop\dataset\mnist_test\*'train_dir=r'C:\Users\River\Desktop\dataset\mnist_test\train\*'test_dir=r'C:\Users\River\Desktop\dataset\mnist_test\test\*'for class_dir in tqdm(glob(data_dir)):#分层抽样file_name_=os.path.join(class_dir,'*.png')file_lst=glob(file_name_)random.shuffle(file_lst)#随机打乱nums=len(file_lst)//kfor i, tt_path in enumerate(zip(glob(train_dir),glob(test_dir))):#移动数据train_path=tt_path[0]test_path=tt_path[1]lft=nums*irt=nums*(i+1)train_lst=file_lst[:lft]+file_lst[rt:]test_lst=file_lst[lft:rt]for img_path in train_lst:shutil.copy(img_path,train_path)for img_path in test_lst:shutil.copy(img_path,test_path)


(6) 利用python或matlab实现“自助法”拆分数据集。
注:自行随机生成数据集(特征+标签),或调用scikit-learn内置数据集。
原理:自助法:留出法每次从数据集 D 中抽取一个样本加入数据集 D′ 中,然后再将该样本放回到原数据集 D 中,即 D 中的样本可以被重复抽取。这样,D 中的一部分样本会被多次抽到,而另一部分样本从未被抽到。假设抽取 m 次,则在 m 次抽样中都没有被抽到的概率为 (1−1/m)m,取极限有:
也就是说,原数据集 D 中约 36.8% 的数据未在 D′ 中出现过,所以我们可以将 D′ 作为训练集,将 D−D′(D 与 D′ 的差集)作为测试集。
说明:自助法适用于数据集较小,难以划分训练、验证集的情况。

def Self_help_method():#自助法data_dir = r'mnist_test\*'train_dir = r'train'test_dir = r'test'file_lst = []train_lst = []for class_dir in tqdm(glob(data_dir)):  # 得到所有文件绝对路径file_name_ = os.path.join(class_dir, '*.png')file_lst += glob(file_name_)sum_nums = len(file_lst)for i in range(sum_nums):  # 进行sum_nums次抽取train_lst.append(file_lst[int(random.random() * sum_nums)])train_lst = set(train_lst)  # 去除重复test_lst = set(file_lst).difference(train_lst)  # 求集合差集得验证集train_value = []test_value = []for img_path in train_lst:# train_value.append(int(img_path.split('\\')[6]))shutil.copy(img_path, train_dir)for img_path in test_lst:# test_value.append(int(img_path.split('\\')[6]))shutil.copy(img_path, test_dir)

抽取10000次(数据集数量为10000张)的数据分布情况:


【实验项目2】
不涉及PR、ROC曲线时,本实验根据预测和标签直接构造混淆矩阵(不懂的建议自行百度,非常重要),然后直接计算各种指标(对数组结果直接取平均)
混淆矩阵:

TP 表示正确分出正例的数量;FN 表示把正例错分为反例的数量;TN 表示正确分出反例的数量;
FP表示把反例错分为正例的数量
举例:

求混淆矩阵以及下述要求的各种指标代码:

class SegmentationMetric(object):def __init__(self, numClass):self.numClass = numClassself.confusionMatrix = np.zeros((self.numClass,) * 2)def genConfusionMatrix(self, imgPredict, imgLabel):  # 同FCN中score.py的fast_hist()函数# remove classes from unlabeled pixels in gt image and predictmask = (imgLabel >= 0) & (imgLabel < self.numClass)label = self.numClass * imgLabel[mask] + imgPredict[mask]count = np.bincount(label, minlength=self.numClass ** 2)confusionMatrix = count.reshape(self.numClass, self.numClass)return confusionMatrixdef addBatch(self, imgPredict, imgLabel):assert imgPredict.shape == imgLabel.shapeself.confusionMatrix += self.genConfusionMatrix(imgPredict, imgLabel)def reset(self):self.confusionMatrix = np.zeros((self.numClass, self.numClass))def Accuracy(self):#准确率#  PA = acc = (TP + TN) / (TP + FN + FP + TN)acc = np.diag(self.confusionMatrix).sum() / self.confusionMatrix.sum()error_rate=1-acc#错误率return acc,error_ratedef Precision(self):# return each category pixel accuracy(A more accurate way to call it precision)# acc = (TP) / (TP + FP)precision = np.diag(self.confusionMatrix) / self.confusionMatrix.sum(axis=0)return np.nanmean(precision)  def F1(self):# F1 = (2*recall*precision) / (recall+precision)f1 = (2*self.Recall()*self.Precision())/(self.Recall()+self.Precision())return f1def Recall(self):# recall = (TP) / (TP + FN)recall = np.diag(self.confusionMatrix) / self.confusionMatrix.sum(axis=1)return np.nanmean(recall)def FPR(self):# recall = (FP) / (FP + TN)FP=self.confusionMatrix.sum(axis=0)-np.diag(self.confusionMatrix)FP_TN=np.repeat(self.confusionMatrix.sum(),self.numClass)-np.sum(self.confusionMatrix, axis=1)fpr = FP/FP_TNreturn np.nanmean(fpr)

(4) 利用python或matlab实现“错误率”、 “准确率” 指标的计算;
准确率 = (TP + TN) / (TP + FN + FP + TN)
错误率=1-准确率
(5) 利用python或matlab实现“精确度” 、“召回率” 、“F1”指标的计算, 并绘制Precision-Recall(PR)曲线,计算曲线下面积;
精确度= (TP) / (TP + FP)
召回率= (TP) / (TP + FN)
F1= (2召回率精确度) / (召回率+精确度)
绘制曲线:
以这实验为例(10分类),由模型计算得到置信度矩阵([*,10]),从中依次取不同的数作为阈值,以某个图片预测为例,它有10个预测概率,当第0个概率值大于等于阈值(数组从0开始),同时标签又为0时(假设为0),那么0这个类别的TP+1,若第1个概率值大于等于阈值,此时标签对不上就FP+1;同理,概率值小于阈值,标签对上,FN+1,或者标签对不上就TN+1,类似地依次进行。 再根据精确度,召回率公式求出其坐标
最终一个阈值就可以确定一组P-R值作为坐标,注意PR必过(0,1)点。最后将其画出即可
可以将曲线(其实是折线)下的多边形分割成多个直角梯形求面积
(6) 利用python或matlab实现“真阳性率” 、“假阳性率”指标的计算,并绘制ROC曲线,计算曲线下面积。
注:可自行生成预测分值列表和标签列表用于测试代码,或调用scikit-learn内置数据集和预测方法。
真阳性率= TP / ( TP+FN )
假阳性率= FP / ( FP + TN )
和PR曲线绘制同理,注意ROC曲线必过(0,0)、(1,1)两点
面积与(5)同理

绘制曲线以及计算面积代码:

def cal_S(macro_recall, macro_precis):  # 划分为小梯形计算S = .0x_l, y_l = macro_recall[0], macro_precis[0]for x_r, y_r in zip(macro_recall[1:], macro_recall[1:]):S += (y_l + y_r) * (x_r - x_l) / 2print(S)def plot(xlabel, ylabel, title, x, y):fig = plt.figure()plt.xlim([-0.01, 1.01])plt.ylim([-0.01, 1.01])plt.xlabel(xlabel)plt.ylabel(ylabel)plt.title(title)plt.plot(x, y)plt.savefig('./' + title + '.png')plt.close(fig)def cal_plot_pr_roc(num_classes=10, score_path="./net_out.txt.txt"):with open(score_path, 'r') as f:files = f.readlines()  # 读取文件macro_precis = []macro_recall = []macro_FPR = [0]lis_all = []for file in tqdm(files):a = file.strip().split(" ")lis_all += a[1:]lis_order = sorted(set(lis_all))  # 记录所有得分情况,并去重从小到大排序,寻找各个阈值点for i in tqdm(lis_order[::100]):  # 每100个点取一个阈值,数越大,迭代次数越少,速度越快,但图的精度会降低true_p = np.zeros(num_classes)  # 真阳true_n = np.zeros(num_classes)  # 真阴false_p = np.zeros(num_classes)  # 假阳false_n = np.zeros(num_classes)  # 假阴for file in files:file_c = file.strip().split(" ")  # 分别计算比较各个类别的得分,分开计算,各自为二分类# 最后求平均,得出宏prfor j in range(num_classes):if float(file_c[j + 1]) >= float(i) and int(file_c[0]) == j:  # 遍历所有样本,第0类为正样本,其他类为负样本,true_p[j] = true_p[j] + 1  # 大于等于阈值,并且真实为正样本,即为真阳,elif float(file_c[j + 1]) >= float(i) and int(file_c[0]) != j:  # 大于等于阈值,真实为负样本,即为假阳;false_p[j] = false_p[j] + 1  # 小于阈值,真实为正样本,即为假阴elif float(file_c[j + 1]) < float(i) and int(file_c[0]) == j:false_n[j] = false_n[j] + 1else:true_n[j] = true_n[j] + 1prec = np.zeros(num_classes)recall = np.zeros(num_classes)FPR = np.zeros(num_classes)for j in range(num_classes):prec[j] = (true_p[j] + 0.00000000001) / (true_p[j] + false_p[j] + 0.00000000001)recall[j] = (true_p[j] + 0.00000000001) / (true_p[j] + false_n[j] + 0.00000000001)FPR[j] = (false_p[j] + 0.00000000001) / (true_n[j] + false_p[j] + 0.00000000001)  # 计算各类别的召回率,小数防止分母为0precision = prec.mean()recall = recall.mean()  # 多分类求得平均精确度和平均召回率,即宏macro_prFPR = FPR.mean()macro_FPR.append(FPR)macro_precis.append(precision)macro_recall.append(recall)# 添加必过点macro_FPR.append(1)TPR = macro_recall.copy()#一定要copy,直接赋值往往会因为变量名的原因重复操作TPR.insert(0, 0)TPR.append(1)macro_precis.append(1)macro_recall.append(0)# 保证顺序以形成一个函数曲线z = list(zip(macro_recall, macro_precis))roc = list(zip(macro_FPR, TPR))z = sorted(z)roc = sorted(roc)macro_recall, macro_precis = zip(*z)macro_FPR, TPR = zip(*roc)x, y = np.array(macro_recall), np.array(macro_precis)roc_x, roc_y = np.array(macro_FPR), np.array(TPR)# 计算面积print('PR_S:')cal_S(x, y)print('ROC_S:')cal_S(roc_x, roc_y)# 画图plot(xlabel='recall', ylabel='precision', title='PR_curve', x=x, y=y)plot(xlabel='FPR', ylabel='TPR', title='ROC_curve', x=roc_x, y=roc_y)

【实验项目3】
(7) 尝试利用python或matlab,采用“交叉验证”或者其它方式,评估机器学习算法的性能,了解评估流程。
本实验采用自助法划分训练集验证集(最好采用交叉验证,说服力最高),像大部分机器学习实验一样,不再单独划分测试集。分别使用pytorch框架自带的resnet18模型和我自己设计的微小模型进行训练,评估。实验平台window10,GPU 3080Ti(这种微小数据集cpu也能跑)

从图中可以看出resnet18的准确率和查全率都很高(f1可以说明)

My_net的PR、ROC曲线:

Resnet18的PR、ROC曲线:


模型简单评估:在同样的学习率、迭代次数下,很明显resnet18的PR曲线和ROC曲线下面积都比My_net的大,说明准确率、精度、查全率更高,从模型精度角度说明resnet18更优,但resnet18有18层网络,而My_net只有2层, My_net更快更小。

下面我贴出可以适用任何简单的分类数据集的数据加载、训练、测试代码,实质上就是本次实验所有的代码和数据,在百度网盘中,下载到本地改一下文件目录就可以运行,尽量没有使用框架。
链接:https://pan.baidu.com/s/1YWbi6a3SM45qrF7W7J1Kog?pwd=nafw
提取码:nafw

有不足之处希望指出我会改正,如果有其他需求可以评论区提问!欢迎点赞收藏!

机器学习第一次上机报告相关推荐

  1. c语言上机第一次实验报告怎么写,C语言程序设计-实验第一次上机实验报告.doc...

    C语言程序设计-实验第一次上机实验报告 2.第一次实验 C语言程序设计 实验报告 专业 计算机科学与技术 班级 信安1302班 日期 2014.3.22 成绩 实验组别 第 1 次实验 表达式和标准输 ...

  2. C语言2020年作业,2020年c语言上机报告范文【四篇】

    <2020年c语言上机报告范文[四篇]>由会员分享,可在线阅读,更多相关<2020年c语言上机报告范文[四篇](7页珍藏版)>请在人人文库网上搜索. 1.2020 年 c 语言 ...

  3. 计算机图形学第一次上机——中点线算法和中点圆算法

    计算机图形学第一次上机实验 课程实验报告 目录 计算机图形学第一次上机实验 课程实验报告 一.实验目的 二.实验环境 三.实验内容 1.中点线算法 2.中点圆算法 四.实验心得 附录:程序源代码 一. ...

  4. 天津理工大学c语言实验2答案,天津理工大学-c语言上机报告2.doc

    天津理工大学-c语言上机报告2 PAGE PAGE 6 实验二 (2009 实验名称: 数据类型与实体. 实验目的: 掌握C语言数据类型,熟悉定义整型.浮点型.字符型变量的定义.赋值与初始化方法: 掌 ...

  5. 天津理工上机c语言报告5,天津理工大学c语言上机报告7.doc

    天津理工大学c语言上机报告7 天津理工大学 计算机与通信工程学院 实验试做报告 2009 至 2010 学年 第 一 学期 课程名称高级语言程序设计I学生专业信息安全实验(7)实验名称指针实验课时4课 ...

  6. c语言上机报告之水仙花数,C语言上机报告之水仙花数..doc

    C语言上机报告之水仙花数. C语言程序设计 上机报告 课题名称:水仙花数的算法 院 (系):工程学院 专业班 级: 052126 学生姓名: 喻培 学 号: 20121004040 指导教师: 熊慕舟 ...

  7. Java第一次上机实验源代码

    小学生计算题: package 第一次上机实验_; import java.util.*; public class 小学计算题 { public static void main(String[] ...

  8. c语言作业重庆科技学院,C语言程序设计学生上机报告-NO3.doc

    C语言程序设计学生上机报告-NO3.doc 重庆科技学院 上机实验报告(上机操作类) 课程名称 C 语 言 程 序 设 计 实验项目 循环结构程序设计(一) 机房名称 I313 上机时间 2017 年 ...

  9. 天津理工大学c语言上机题库,天津理工大学C语言上机报告题目加答案.doc

    天津理工大学C语言上机报告题目加答案 实验五 (2009-9-24) 实验名称: 数组. 实验目的: 掌握C语言中一维数组.二维数组的定义方法: 掌握字符数组与常用字符串处理函数的使用: 掌握与数组有 ...

最新文章

  1. 千万别中招!手把手教你复现Log4j2漏洞!
  2. php设计是什么意思,php的设计模式是什么
  3. Js文件中调用其它Js函数的方法(转)
  4. vmware-tools安装
  5. python 使用小知识总结(持续更新ing)
  6. 全国计算机等级考试题库二级C操作题100套(第31套)
  7. Android设计模式之——观察者模式
  8. 除去数组中的空字符元素array_filter()
  9. python优雅编程_Python优雅编程——Collections模块中的高性能数据类型
  10. onvif学习笔记7:一个C++封装的onvif代码的阅读笔记
  11. 慢日志定位到备份锁表Waiting for global read lock原因
  12. python3打造专属的下载软件
  13. SPSS——统计描述
  14. python跑完代码后怎么办_2017/06/14跑成功了的代码,FYI
  15. 数据链路层详细解剖,并完成实现不同交换机相同VLAN主机之间的通信实验
  16. java hadoop是什么_hadoop是什么语言
  17. oracle教程课件,Oracle入门教程(PPT课件)
  18. 你是阳光,你的世界充满阳光---心在哪,成就就在哪
  19. https://www.npmjs.com/一个下载库的网址
  20. c语言是汇编语言实现的吗,使用汇编语言实现逻辑表达式

热门文章

  1. 用python制作一个简单的在线单词翻译器
  2. [jzoj 3912] 超氧化钾 {约数+除数分块}
  3. content-box和border-box的区别
  4. Photoshop图片去水印-photoshop图片去水印怎么去
  5. CAN数据传输顺序,及仲裁过程
  6. 阿里云实名认证常见问题分析
  7. mysql练习-单表查询和多表查询
  8. php哪些品牌,php虚拟主机哪个品牌的好
  9. request.getRealPath 替代方法
  10. 亚马逊跨境电商平台有什么特点