机器学习实战(一):K-近邻算法

机器学习实战(一):K-近邻算法

一、K-近邻算法概述

1、工作原理
存在一个样本数据集合,也称作为训练样本集,并且样本集中每个数据都存在标签,即我们知道样本集中每一个数据与所属分类的对应关系。输入没有标签的新数据后,将新的数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取样本最相似数据(最近邻)的分类标签。一般来说,我们只选择样本数据集中前k个最相似的数据,这就是k-近邻算法中k的出处,通常k是不大于20的整数。最后,选择k个最相似数据中出现次数最多的分类,作为新数据的分类。
2、特点
优点:精度高、对异常值不敏感、无数据输入假定
缺点:计算复杂度高、空间复杂度高
适用数据范围:数值型和标称型
3、实战实例:电影类别分类、约会网站配对效果判定、手写数字识别。
k-近邻算法的一般流程
1)收集数据:可以使用爬虫进行数据的收集,也可以使用第三方提供的免费或收费的数据。一般来讲,数据放在txt文本文件中,按照一定的格式进行存储,便于解析及处理。
2)准备数据:使用Python解析、预处理数据。
3)分析数据:可以使用很多方法对数据进行分析,例如使用Matplotlib将数据可视化。
4)测试算法:计算错误率。
5)使用算法:错误率在可接受范围内,就可以运行k-近邻算法进行分类。

二、实战实例

1、电影类别分类
k-近邻算法步骤如下:
1)计算已知类别数据集中的点与当前点之间的距离;
2)按照距离递增次序排序;
3)选取与当前点距离最小的k个点;
4)确定前k个点所在类别的出现频率;
5)返回前k个点所出现频率最高的类别作为当前点的预测分类。

# 科学计算包 numpy
import numpy as np
# 运算符模块
import collections

"""
函数说明:创建数据集和标签
Returns:
        group - 数据集
        labels - 分类标签
"""
def createDataSet():
    # 四组二维特征
    group = np.array([[1, 101], [5, 89], [108, 5], [115, 8]])
    # 四组特征的标签
    labels = ['爱情片', '爱情片', '动作片', '动作片']
    return group, labels

"""
函数说明:KNN算法,分类器
Parames:
        intX - 用于分类的数据(测试集)
        dataSet - 用于训练的数据(训练集)
        labels - 分类标签
        K - KNN算法参数,选择距离最小的k个点
Returns:
        sortedClassCount[0][0] - 分类结果        

"""
def classify0(inX, dataSet, labels, k):
    # 计算距离
    dist = np.sum((inX - dataSet)**2, axis=1)**0.5
    # k个最近的标签
    k_labels = [labels[index] for index in dist.argsort()[0:k]]
    # 出现次数最多的标签即为最终标签 most_common(1)[0][0] 获取最高频元素的频次
    label = collections.Counter(k_labels).most_common(1)[0][0]
    return label

if __name__ == '__main__':
    # 创建数据集
    group, labels = createDataSet()
    # 测试集
    test = [101, 20]
    # KNN分类
    test_class = classify0(test, group, labels, 3)
    # 打印分类结果
    print(test_class)

结果:动作片

"""
函数说明:KNN算法,分类器
k-近邻算法步骤:
    1.计算已知类别数据集中的点与当前点之间的距离 欧式距离:[(x2-x1)^2+(y2-y1)^2]**0.5
    2.按照距离递增次序排列
    3.选取与当前距离最小的k个点
    4.确定前k个点所在类别的出现频率
    5.返回前k个点出现频率最高的类别作为当前点的预测分类
Parames:
        intX - 用于分类的数据(测试集)
        dataSet - 用于训练的数据(训练集)
        labels - 分类标签
        K - KNN算法参数,选择距离最小的k个点
Returns:
        sortedClassCount[0][0] - 分类结果        

"""
def classify0(inX, dataSet, labels, k):
    # numpy函数shape[0]返回dataSet的行数
    dataSetSize = dataSet.shape[0]
    # 将第dataSetSize行的数据复制,放在测试集后,数据集
    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
    # 二维特征相减后平方
    sqDiffMat = diffMat**2
    # 所有元素相加 sum(0)列相加 sum(1)行相加
    sqDistances = sqDiffMat.sum(axis=1)
    # 开方,计算距离
    distances = sqDistances**0.5
    # 返回distances中元素从小到大排序后的索引值
    sortedDistIndex = distances.argsort()
    # 一个记录类别次数的字典
    classCount = {}
    for i in range(k):
        # 取出前k个元素类别
        voteLabel = labels[sortedDistIndex[i]]
        # get(key, default=None), 字典的get()方法, 返回指定键的值, 如果值不在字典中返回默认值
        # 计算类别次数
        classCount[voteLabel] = classCount.get(voteLabel, 0) + 1
        # 降序排列字典
        # key=operator.itemgetter(1)根据字典的值进行排序
        # key=operator.itemgetter(0)根据字典的键进行排序
        sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
        # 返回次数最多的类别
        return sortedClassCount[0][0]

2、约会网站配对效果判定

  • 1.实战背景
    1)分类:
    1.不喜欢的人 didntLike
    2.魅力一般的人 smallDoses
    3.极具魅力的人 largeDoses
    2)样本数据主要包含以下3种特征:
    1.每年获得的飞行常客里程数
    2.玩视频游戏所消耗时间百分比
    3.每周消费的冰淇淋公升数
    数据存放在文本文件datingTestSet.txt中,每个样本数据占据一行,总共有1000行
  • 2.准备数据:数据分析
    将待处理的数据的格式改变为分类器可以接收的格式,将数据分类两部分,即特征矩阵和对应的分类标签向量;创建名为file2matrix的函数,以此来处理输入格式问题

在这里插入图片描述
导入数据并将其转换为想要的的格式后,虽然可以浏览文本文件,但并不友好,因此将数据可视化

  • 3.分析数据,数据可视化,创建散点图

在这里插入图片描述

  • 4.准备数据,数据归一化
    样本数据:
    在这里插入图片描述
    在处理不同范围的特征值时,通常采用的方法是将数值归一化,将取值范围处理为0到1或者-1到1之间。下面的公式可以将任意取值范围的特征值转化为0到1区间内的值:newValue = (oldValue - min) / (max - min)其中min和max分别是数据集中的最小特征值和最大特征值
    在这里插入图片描述
    顺利将数据归一化了,并且求出了数据的取值范围和数据的最小值
  • 5.测试算法,验证分类器
    评估算法的正确率,通常我们只提供已有数据的90%作为训练样本来训练分类器,而使用其余的10%数据去测试分类器,检测分类器的正确率,注意随机选择
    在这里插入图片描述
  • 6.使用算法,构建完整可用系统

在这里插入图片描述
代码汇总:

import numpy as np
import operator
import matplotlib.lines as mlines
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties

"""
函数说明:KNN算法,分类器
k-近邻算法步骤:
    1.计算已知类别数据集中的点与当前点之间的距离 欧式距离:[(x2-x1)^2+(y2-y1)^2]**0.5
    2.按照距离递增次序排列
    3.选取与当前距离最小的k个点
    4.确定前k个点所在类别的出现频率
    5.返回前k个点出现频率最高的类别作为当前点的预测分类
Parames:
        intX - 用于分类的数据(测试集)
        dataSet - 用于训练的数据(训练集)
        labels - 分类标签
        K - KNN算法参数,选择距离最小的k个点
Returns:
        sortedClassCount[0][0] - 分类结果        

"""
def classify0(inX, dataSet, labels, k):
    # numpy函数shape[0]返回dataSet的行数
    dataSetSize = dataSet.shape[0]
    # 将第dataSetSize行的数据复制,放在测试集后,数据集
    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
    # 二维特征相减后平方
    sqDiffMat = diffMat**2
    # 所有元素相加 sum(0)列相加 sum(1)行相加
    sqDistances = sqDiffMat.sum(axis=1)
    # 开方,计算距离
    distances = sqDistances**0.5
    # 返回distances中元素从小到大排序后的索引值
    sortedDistIndex = distances.argsort()
    # 一个记录类别次数的字典
    classCount = {}
    for i in range(k):
        # 取出前k个元素类别
        voteLabel = labels[sortedDistIndex[i]]
        # get(key, default=None), 字典的get()方法, 返回指定键的值, 如果值不在字典中返回默认值
        # 计算类别次数
        classCount[voteLabel] = classCount.get(voteLabel, 0) + 1
        # 降序排列字典
        # key=operator.itemgetter(1)根据字典的值进行排序
        # key=operator.itemgetter(0)根据字典的键进行排序
        sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
        # 返回次数最多的类别
        return sortedClassCount[0][0]

"""
函数说明:打开并解析文件,对数据进行分类 
            1不喜欢 2魅力一般 3极具魅力
Parameters:
            filename - 文件名
Returns:
        returnMat - 矩阵特征
        classLabelVector - 分类label向量
步骤:
    1.打开文件,得到文件行数
    2.创建以0填充的NumPy矩阵
    3.循环处理文件中的每行数据
        1)使用line.strip()截取所有回车字符
        2)使用'\t'分割成一个元素列表
        3)取前3个元素,存储到矩阵中,也就是特征矩阵
        4)利用索引值-1(最后一列元素),将最后一列存储向量classLabelVector
            注意:变量值类型为整型
"""
def file2matrix(filename):
    # 1.打开文件,得到文件行数
    fr = open(filename, 'r', encoding='utf-8')
    arrayLines = fr.readlines()
    # lstrip - 截掉字符串左边的空格或指定字符
    # 针对有BOM的UTF-8文本,应该去掉BOM,否则后面会引发错误
    arrayLines[0] = arrayLines[0].lstrip('\ufeff')
    numberOfLines = len(arrayLines)

    # 2.创建以0填充的NumPy矩阵
    returnMat = np.zeros((numberOfLines, 3))

    # 3.循环处理文件中的每行数据
    classLabelVector = []  # 分类标签向量
    index = 0  # 行的索引值
    for line in arrayLines:
        line = line.strip()  # 1)使用line.strip()截取所有回车字符
        listFromLine = line.split('\t')  # 2)使用'\t'分割成一个元素列表
        returnMat[index, :] = listFromLine[0:3]  # 3)取前3个元素,存储到矩阵中,也就是特征矩阵
        # 4)利用索引值 - 1(最后一列元素),将最后一列存储向量classLabelVector
        # 根据文本标记的喜欢程度进行分类,1不喜欢 2魅力一般 3极具魅力
        if listFromLine[-1] == 'didntLike':
            classLabelVector.append(1)
        elif listFromLine[-1] == 'smallDoses':
            classLabelVector.append(2)
        elif listFromLine[-1] == 'largeDoses':
            classLabelVector.append(3)
        index += 1
    return returnMat, classLabelVector

"""
函数说明:可视化数据
Parameters:
            datingDataMat - 特征矩阵
            datingLabels - 分类label
"""
def showDatas(datingDateMat, datingLabels):
    # 设置汉字格式
    font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14)
    # 将fig画布分隔成1行1列,不共享x轴和y轴,fig画布的大小为(13,8)
    # 当nrow=2,nclos=2时,代表fig画布被分为四个区域,axs[0][0]表示第一行第一个区域
    fig, axs = plt.subplots(nrows=2, ncols=2, sharex=False, sharey=False, figsize=(13, 8))

    numberOfLabels = len(datingLabels)
    LabelsColors = []
    for i in datingLabels:
        if i == 1:
            LabelsColors.append('black')
        if i == 2:
            LabelsColors.append('orange')
        if i == 3:
            LabelsColors.append('red')
    # 画出散点图,以datingDataMat矩阵的第一(飞行常客例程)、第二列(玩游戏)数据画散点数据,散点大小为15,透明度为0.5
    axs[0][0].scatter(x=datingDataMat[:, 0], y=datingDataMat[:, 1], color=LabelsColors, s=15, alpha=.5)
    # 设置标题,x轴label,y轴label
    axs0_title_text = axs[0][0].set_title(u'每年获得的飞行常客里程数与玩视频游戏所消耗时间占比', FontProperties=font)
    axs0_xlabel_text = axs[0][0].set_xlabel(u'每年获得的飞行常客里程数', FontProperties=font)
    axs0_ylabel_text = axs[0][0].set_ylabel(u'玩视频游戏所消耗时间占比', FontProperties=font)
    plt.setp(axs0_title_text, size=9, weight='bold', color='red')
    plt.setp(axs0_xlabel_text, size=7, weight='bold', color='black')
    plt.setp(axs0_ylabel_text, size=7, weight='bold', color='black')

    # 画出散点图,以datingDataMat矩阵的第一(飞行常客例程)、第三列(冰激凌)数据画散点数据,散点大小为15,透明度为0.5
    axs[0][1].scatter(x=datingDataMat[:, 0], y=datingDataMat[:, 2], color=LabelsColors, s=15, alpha=.5)
    # 设置标题,x轴label,y轴label
    axs1_title_text = axs[0][1].set_title(u'每年获得的飞行常客里程数与每周消费的冰激淋公升数', FontProperties=font)
    axs1_xlabel_text = axs[0][1].set_xlabel(u'每年获得的飞行常客里程数', FontProperties=font)
    axs1_ylabel_text = axs[0][1].set_ylabel(u'每周消费的冰激淋公升数', FontProperties=font)
    plt.setp(axs1_title_text, size=9, weight='bold', color='red')
    plt.setp(axs1_xlabel_text, size=7, weight='bold', color='black')
    plt.setp(axs1_ylabel_text, size=7, weight='bold', color='black')

    # 画出散点图,以datingDataMat矩阵的第二(玩游戏)、第三列(冰激凌)数据画散点数据,散点大小为15,透明度为0.5
    axs[1][0].scatter(x=datingDataMat[:, 1], y=datingDataMat[:, 2], color=LabelsColors, s=15, alpha=.5)
    # 设置标题,x轴label,y轴label
    axs2_title_text = axs[1][0].set_title(u'玩视频游戏所消耗时间占比与每周消费的冰激淋公升数', FontProperties=font)
    axs2_xlabel_text = axs[1][0].set_xlabel(u'玩视频游戏所消耗时间占比', FontProperties=font)
    axs2_ylabel_text = axs[1][0].set_ylabel(u'每周消费的冰激淋公升数', FontProperties=font)
    plt.setp(axs2_title_text, size=9, weight='bold', color='red')
    plt.setp(axs2_xlabel_text, size=7, weight='bold', color='black')
    plt.setp(axs2_ylabel_text, size=7, weight='bold', color='black')
    # 设置图例
    didntLike = mlines.Line2D([], [], color='black', marker='.',
                              markersize=6, label='didntLike')
    smallDoses = mlines.Line2D([], [], color='orange', marker='.',
                               markersize=6, label='smallDoses')
    largeDoses = mlines.Line2D([], [], color='red', marker='.',
                               markersize=6, label='largeDoses')
    # 添加图例
    axs[0][0].legend(handles=[didntLike, smallDoses, largeDoses])
    axs[0][1].legend(handles=[didntLike, smallDoses, largeDoses])
    axs[1][0].legend(handles=[didntLike, smallDoses, largeDoses])
    # 显示图片
    plt.show()


"""
函数说明:对数据归一化
Parameters:
            dataSet - 特征矩阵
Returns:
        normDataSet - 归一化后的特征矩阵
        ranges - 数据范围
        minVals - 数据最小值
"""
def autoNorm(dataSet):
    # 获得数据的最值
    minVals = dataSet.min(0)
    maxVals = dataSet.max(0)
    # 最大值和最小值范围
    ranges = maxVals - minVals
    # 返回dataSet的矩阵行列数
    normDataSet = np.zeros(np.shape(dataSet))
    # 行数
    row = dataSet.shape[0]
    # 原始值减去最小值
    normDataSet = dataSet - np.tile(minVals, (row, 1))
    # 除以最大和最小值的差得到归一化数据
    normDataSet /= np.tile(ranges, (row, 1))
    # 返回归一化数据结果,数据范围,最小值
    return normDataSet, ranges, minVals

def datingClassTest():
    # 打开的文件名
    filename = "datingTestSet.txt"
    # 将返回的特征矩阵和分类向量分别存储到datingDataMat和datingLabels中
    datingDataMat, datingLabels = file2matrix(filename)
    # 取所有数据的百分之十
    hoRatio = 0.10
    # 数据归一化,返回归一化后的矩阵,数据范围,数据最小值
    normMat, ranges, minVals = autoNorm(datingDataMat)
    # 获得normMat的行数
    row = normMat.shape[0]
    # 百分之十的测试数据的个数
    numTestVecs = int(row * hoRatio)
    # 分类错误计数
    errorCount = 0.0
    for i in range(numTestVecs):
        # 前numTestVecs个数据作为测试集,后m-numTestVecs个数据作为训练集
        classifierResult = classify0(normMat[i, :], normMat[numTestVecs:row, :],
            datingLabels[numTestVecs:row], 4)
        print("分类结果:%s\t真实类别:%d" % (classifierResult, datingLabels[i]))
        if classifierResult != datingLabels[i]:
            errorCount += 1.0
    print("错误率:%f%%" % (errorCount / float(numTestVecs) * 100))

def classifyPerson():
    # 输出结果
    resultList = ['讨厌', '有些喜欢', '非常喜欢']
    # 三维特征用户输入
    precentTats = float(input("玩视频游戏所耗时间百分比:"))
    ffMiles = float(input("每年获得的飞行常客里程数:"))
    iceCream = float(input("每周消费的冰激淋公升数:"))
    # 打开的文件名
    fileName = "datingTestSet.txt"
    # 打开并处理数据
    datingDataMat, datingLabels = file2matrix(fileName)
    # 训练集归一化
    normMat, ranges, minVals = autoNorm(datingDataMat)
    # 生成NumPy数组,测试集
    inArr = np.array([ffMiles, precentTats, iceCream])
    # 测试集归一化
    norminArr = (inArr - minVals) / ranges
    # 返回分类结果
    classifierResult = classify0(norminArr, normMat, datingLabels, 3)
    # 打印结果
    print("你可能%s这个人" % (resultList[classifierResult-1]))

if __name__ == '__main__':
    # 2.准备数据:数据解析
    # filename = "datingTestSet.txt"
    # # 打开并处理数据
    # datingDataMat, datingLabels = file2matrix(filename)
    # print(datingDataMat)
    # print(datingLabels)

    # 3.分析数据,数据可视化
    # filename = "datingTestSet.txt"
    # # 打开并处理数据
    # datingDataMat, datingLabels = file2matrix(filename)
    # showDatas(datingDataMat, datingLabels)

    # 4.准备数据,数据归一化
    # filename = "datingTestSet.txt"
    # # 打开并处理数据
    # datingDataMat, datingLabels = file2matrix(filename)
    # normDataSet, ranges, minVals = autoNorm(datingDataMat)
    # print(normDataSet)
    # print(ranges)
    # print(minVals)

    # 5.测试算法,验证分类器
    # datingClassTest()

    # 6.使用算法:构建完整可用系统
    classifyPerson()

3、手写识别系统

  • 1.实战背景
    需要识别的数字使用图形处理软件,处理成具有相同的色彩和大小:宽高是32像素x32像素,采用本文格式存储图像;文本格式存储的数字的文件命名格式为:数字的值_该数字的样本序号,数据集分为训练集和测试集
    在这里插入图片描述
  • 2.代码汇总
import numpy as np
import operator
from os import listdir

"""
函数说明:KNN算法,分类器
k-近邻算法步骤:
    1.计算已知类别数据集中的点与当前点之间的距离 欧式距离:[(x2-x1)^2+(y2-y1)^2]**0.5
    2.按照距离递增次序排列
    3.选取与当前距离最小的k个点
    4.确定前k个点所在类别的出现频率
    5.返回前k个点出现频率最高的类别作为当前点的预测分类
Parames:
        intX - 用于分类的数据(测试集)
        dataSet - 用于训练的数据(训练集)
        labels - 分类标签
        K - KNN算法参数,选择距离最小的k个点
Returns:
        sortedClassCount[0][0] - 分类结果        

"""
def classify0(inX, dataSet, labels, k):
    # numpy函数shape[0]返回dataSet的行数
    dataSetSize = dataSet.shape[0]
    # 将第dataSetSize行的数据复制,放在测试集后,数据集
    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
    # 二维特征相减后平方
    sqDiffMat = diffMat**2
    # 所有元素相加 sum(0)列相加 sum(1)行相加
    sqDistances = sqDiffMat.sum(axis=1)
    # 开方,计算距离
    distances = sqDistances**0.5

    # 返回distances中元素从小到大排序后的索引值
    sortedDistIndex = distances.argsort()

    # 一个记录类别次数的字典
    classCount = {}
    for i in range(k):
        # 取出前k个元素类别
        voteLabel = labels[sortedDistIndex[i]]
        # get(key, default=None), 字典的get()方法, 返回指定键的值, 如果值不在字典中返回默认值
        # 计算类别次数
        classCount[voteLabel] = classCount.get(voteLabel, 0) + 1
        # 降序排列字典
        # key=operator.itemgetter(1)根据字典的值进行排序
        # key=operator.itemgetter(0)根据字典的键进行排序
        sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
        # 返回次数最多的类别
        return sortedClassCount[0][0]

"""
函数说明:将32*32的二进制图像转换为1*1024向量
Parameters:
            fileName - 文件名
Returns:
        returnVect - 返回的二进制图像的*1024向量
"""
def img2Vector(fileName):
    # 创建1*1024向量
    returnVect = np.zeros((1, 1024))
    # 打开文件
    fr = open(fileName)
    # 按行读取
    for i in range(32):
        # 读一行数据
        lineStr = fr.readline()
        # 将每一行的前32个元素一次添加到returnVect中
        for j in range(32):
            returnVect[0, 32*i+j] = int(lineStr[j])
    # 返回转换后的1*1024向量
    return returnVect

"""
函数说明:手写数字分类测试
"""
def handWritingClassTest():
    # 测试集的Labels
    hwLabels = []
    # 返回trainingDigits目录下的文件名
    trainingFileList = listdir('trainingDigits')
    # 返回文件夹下的文件个数
    m = len(trainingFileList)
    # 初始化训练的Mat矩阵,测试集
    trainingMat = np.zeros((m, 1024))
    # 从文件命中解析处训练集类型
    for i in range(m):
        # 获得文件的名字
        fileNameStr = trainingFileList[i]
        # 获得分类的数字
        classNumber = int(fileNameStr.split('_')[0])
        # 将获得的类别添加到hwLabels中
        hwLabels.append(classNumber)
        # 将每一个文件的1*1024数据存储到trainingMat矩阵中
        trainingMat[i, :] = img2Vector('trainingDigits/%s' % (fileNameStr))
    # 返回testDigits目录下的文件名
    testFileList = listdir('testDigits')
    # 错误检测计数
    errorCount = 0
    # 测试数据的数量
    mTest = len(testFileList)
    # 从文件中解析出测试集的类别并进行分类测试
    for i in range(mTest):
        # 获得文件的名字
        fileNameStr = testFileList[i]
        # 获得分类的数字
        classNumber = int(fileNameStr.split('_')[0])
        # 获得测试集的1x1024向量,用于训练
        vectorUnderTest = img2Vector('testDigits/%s' % (fileNameStr))
        # 获得预测结果
        classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
        print("分类返回结果为%d\t真实结果为%d" % (classifierResult, classNumber))
        if (classifierResult != classNumber):
            errorCount += 1.0
    print("总共错了%d个数据\n错误率为%f%%" % (errorCount, errorCount / mTest))

if __name__ == '__main__':
    handWritingClassTest()

在这里插入图片描述

4、总结
优点

  • 简单好用,容易理解,精度高,理论成熟,既可以用来做分类也可以用来做回归;
  • 可用于数值型数据和离散型数据;
  • 训练时间复杂度为O(n);无数据输入假定;
  • 对异常值不敏感缺点计算复杂性高;

缺点

  • 空间复杂性高;样本不平衡问题(即有些类别的样本数量很多,而其它样本的数量很少);
  • 一般数值很大的时候不用这个,计算量太大。但是单个样本又不能太少,否则容易发生误分。
  • 最大的缺点是无法给出数据的内在含义
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值