用Python从零实现贝叶斯分类器的机器学习的教程

朴素贝叶斯算法简单高效,在处理分类问题上,是应该首先考虑的方法之一。

通过本教程,你将学到朴素贝叶斯算法的原理和Python版本的逐步实现。

更新:查看后续的关于朴素贝叶斯使用技巧的文章“Better Naive Bayes: 12 Tips To Get The Most From The Naive Bayes Algorithm
朴素贝叶斯分类器,Matt Buck保留部分版权
关于朴素贝叶斯

朴素贝叶斯算法是一个直观的方法,使用每个属性归属于某个类的概率来做预测。你可以使用这种监督性学习方法,对一个预测性建模问题进行概率建模。

给定一个类,朴素贝叶斯假设每个属性归属于此类的概率独立于其余所有属性,从而简化了概率的计算。这种强假定产生了一个快速、有效的方法。

给定一个属性值,其属于某个类的概率叫做条件概率。对于一个给定的类值,将每个属性的条件概率相乘,便得到一个数据样本属于某个类的概率。

我们可以通过计算样本归属于每个类的概率,然后选择具有最高概率的类来做预测。

通常,我们使用分类数据来描述朴素贝叶斯,因为这样容易通过比率来描述、计算。一个符合我们目的、比较有用的算法需要支持数值属性,同时假设每一个数值属性服从正态分布(分布在一个钟形曲线上),这又是一个强假设,但是依然能够给出一个健壮的结果。
预测糖尿病的发生

本文使用的测试问题是“皮马印第安人糖尿病问题”。

这个问题包括768个对于皮马印第安患者的医疗观测细节,记录所描述的瞬时测量取自诸如患者的年纪,怀孕和血液检查的次数。所有患者都是21岁以上(含21岁)的女性,所有属性都是数值型,而且属性的单位各不相同。

每一个记录归属于一个类,这个类指明以测量时间为止,患者是否是在5年之内感染的糖尿病。如果是,则为1,否则为0。

机器学习文献中已经多次研究了这个标准数据集,好的预测精度为70%-76%。

下面是pima-indians.data.csv文件中的一个样本,了解一下我们将要使用的数据。

注意:下载文件,然后以.csv扩展名保存(如:pima-indians-diabetes.data.csv)。查看文件中所有属性的描述。


6,148,72,35,0,33.6,0.627,50,1
1,85,66,29,0,26.6,0.351,31,0
8,183,64,0,0,23.3,0.672,32,1
1,89,66,23,94,28.1,0.167,21,0
0,137,40,35,168,43.1,2.288,33,1

朴素贝叶斯算法教程

教程分为如下几步:

1.处理数据:从CSV文件中载入数据,然后划分为训练集和测试集。

2.提取数据特征:提取训练数据集的属性特征,以便我们计算概率并做出预测。

3.单一预测:使用数据集的特征生成单个预测。

4.多重预测:基于给定测试数据集和一个已提取特征的训练数据集生成预测。

5.评估精度:评估对于测试数据集的预测精度作为预测正确率。

6.合并代码:使用所有代码呈现一个完整的、独立的朴素贝叶斯算法的实现。

1.处理数据

首先加载数据文件。CSV格式的数据没有标题行和任何引号。我们可以使用csv模块中的open函数打开文件,使用reader函数读取行数据。

我们也需要将以字符串类型加载进来属性转换为我们可以使用的数字。下面是用来加载匹马印第安人数据集(Pima indians dataset)的loadCsv()函数。


import csv
def loadCsv(filename):
  lines = csv.reader(open(filename, "rb"))
  dataset = list(lines)
  for i in range(len(dataset)):
    dataset[i] = [float(x) for x in dataset[i]]
  return dataset

我们可以通过加载皮马印第安人数据集,然后打印出数据样本的个数,以此测试这个函数。


filename = 'pima-indians-diabetes.data.csv'
dataset = loadCsv(filename)
print('Loaded data file {0} with {1} rows').format(filename, len(dataset))

运行测试,你会看到如下结果:


Loaded data file iris.data.csv with 150 rows

下一步,我们将数据分为用于朴素贝叶斯预测的训练数据集,以及用来评估模型精度的测试数据集。我们需要将数据集随机分为包含67%的训练集合和包含33%的测试集(这是在此数据集上测试算法的通常比率)。

下面是splitDataset()函数,它以给定的划分比例将数据集进行划分。


import random
def splitDataset(dataset, splitRatio):
  trainSize = int(len(dataset) * splitRatio)
  trainSet = []
  copy = list(dataset)
  while len(trainSet) < trainSize:
    index = random.randrange(len(copy))
    trainSet.append(copy.pop(index))
  return [trainSet, copy]

我们可以定义一个具有5个样例的数据集来进行测试,首先它分为训练数据集和测试数据集,然后打印出来,看看每个数据样本最终落在哪个数据集。


dataset = [[1], [2], [3], [4], [5]]
splitRatio = 0.67
train, test = splitDataset(dataset, splitRatio)
print('Split {0} rows into train with {1} and test with {2}').format(len(dataset), train, test)

运行测试,你会看到如下结果:


Split 5 rows into train with [[4], [3], [5]] and test with [[1], [2]]

提取数据特征

朴素贝叶斯模型包含训练数据集中数据的特征,然后使用这个数据特征来做预测。

所收集的训练数据的特征,包含相对于每个类的每个属性的均值和标准差。举例来说,如果如果有2个类和7个数值属性,然后我们需要每一个属性(7)和类(2)的组合的均值和标准差,也就是14个属性特征。

在对特定的属性归属于每个类的概率做计算、预测时,将用到这些特征。

我们将数据特征的获取划分为以下的子任务:

按类别划分数据
    计算均值
    计算标准差
    提取数据集特征
    按类别提取属性特征

按类别划分数据

首先将训练数据集中的样本按照类别进行划分,然后计算出每个类的统计数据。我们可以创建一个类别到属于此类别的样本列表的的映射,并将整个数据集中的样本分类到相应的列表。

下面的SeparateByClass()函数可以完成这个任务:


def separateByClass(dataset):
  separated = {}
  for i in range(len(dataset)):
    vector = dataset[i]
    if (vector[-1] not in separated):
      separated[vector[-1]] = []
    separated[vector[-1]].append(vector)
  return separated

可以看出,函数假设样本中最后一个属性(-1)为类别值,返回一个类别值到数据样本列表的映射。

我们可以用一些样本数据测试如下:


dataset = [[1,20,1], [2,21,0], [3,22,1]]
separated = separateByClass(dataset)
print('Separated instances: {0}').format(separated)

运行测试,你会看到如下结果:


Separated instances: {0: [[2, 21, 0]], 1: [[1, 20, 1], [3, 22, 1]]}

计算均值

我们需要计算在每个类中每个属性的均值。均值是数据的中点或者集中趋势,在计算概率时,我们用它作为高斯分布的中值。

我们也需要计算每个类中每个属性的标准差。标准差描述了数据散布的偏差,在计算概率时,我们用它来刻画高斯分布中,每个属性所期望的散布。

标准差是方差的平方根。方差是每个属性值与均值的离差平方的平均数。注意我们使用N-1的方法(译者注:参见无偏估计),也就是在在计算方差时,属性值的个数减1。


import math
def mean(numbers):
  return sum(numbers)/float(len(numbers))

def stdev(numbers):
  avg = mean(numbers)
  variance = sum([pow(x-avg,2) for x in numbers])/float(len(numbers)-1)
  return math.sqrt(variance)

通过计算从1到5这5个数的均值来测试函数。


numbers = [1,2,3,4,5]
print('Summary of {0}: mean={1}, stdev={2}').format(numbers, mean(numbers), stdev(numbers))

运行测试,你会看到如下结果:


Summary of [1, 2, 3, 4, 5]: mean=3.0, stdev=1.58113883008

提取数据集的特征

现在我们可以提取数据集特征。对于一个给定的样本列表(对应于某个类),我们可以计算每个属性的均值和标准差。

zip函数将数据样本按照属性分组为一个个列表,然后可以对每个属性计算均值和标准差。


def summarize(dataset):
  summaries = [(mean(attribute), stdev(attribute)) for attribute in zip(*dataset)]
  del summaries[-1]
  return summaries

我们可以使用一些测试数据来测试这个summarize()函数,测试数据对于第一个和第二个数据属性的均值和标准差显示出显著的不同。


dataset = [[1,20,0], [2,21,1], [3,22,0]]
summary = summarize(dataset)
print('Attribute summaries: {0}').format(summary)

运行测试,你会看到如下结果:


Attribute summaries: [(2.0, 1.0), (21.0, 1.0)]

按类别提取属性特征

合并代码,我们首先将训练数据集按照类别进行划分,然后计算每个属性的摘要。


def summarizeByClass(dataset):
  separated = separateByClass(dataset)
  summaries = {}
  for classValue, instances in separated.iteritems():
    summaries[classValue] = summarize(instances)
  return summaries

使用小的测试数据集来测试summarizeByClass()函数。


dataset = [[1,20,1], [2,21,0], [3,22,1], [4,22,0]]
summary = summarizeByClass(dataset)
print('Summary by class value: {0}').format(summary)

运行测试,你会看到如下结果:


Summary by class value:
{0: [(3.0, 1.4142135623730951), (21.5, 0.7071067811865476)],
1: [(2.0, 1.4142135623730951), (21.0, 1.4142135623730951)]}

预测

我们现在可以使用从训练数据中得到的摘要来做预测。做预测涉及到对于给定的数据样本,计算其归属于每个类的概率,然后选择具有最大概率的类作为预测结果。

我们可以将这部分划分成以下任务:

计算高斯概率密度函数
    计算对应类的概率
    单一预测
    评估精度

计算高斯概率密度函数

给定来自训练数据中已知属性的均值和标准差,我们可以使用高斯函数来评估一个给定的属性值的概率。

已知每个属性和类值的属性特征,在给定类值的条件下,可以得到给定属性值的条件概率。

关于高斯概率密度函数,可以查看参考文献。总之,我们要把已知的细节融入到高斯函数(属性值,均值,标准差),并得到属性值归属于某个类的似然(译者注:即可能性)。

在calculateProbability()函数中,我们首先计算指数部分,然后计算等式的主干。这样可以将其很好地组织成2行。


import math
def calculateProbability(x, mean, stdev):
  exponent = math.exp(-(math.pow(x-mean,2)/(2*math.pow(stdev,2))))
  return (1 / (math.sqrt(2*math.pi) * stdev)) * exponent

使用一些简单的数据测试如下:


x = 71.5
mean = 73
stdev = 6.2
probability = calculateProbability(x, mean, stdev)
print('Probability of belonging to this class: {0}').format(probability)

运行测试,你会看到如下结果:

Probability of belonging to this class: 0.0624896575937

计算所属类的概率

既然我们可以计算一个属性属于某个类的概率,那么合并一个数据样本中所有属性的概率,最后便得到整个数据样本属于某个类的概率。

使用乘法合并概率,在下面的calculClassProbilities()函数中,给定一个数据样本,它所属每个类别的概率,可以通过将其属性概率相乘得到。结果是一个类值到概率的映射。


def calculateClassProbabilities(summaries, inputVector):
  probabilities = {}
  for classValue, classSummaries in summaries.iteritems():
    probabilities[classValue] = 1
    for i in range(len(classSummaries)):
      mean, stdev = classSummaries[i]
      x = inputVector[i]
      probabilities[classValue] *= calculateProbability(x, mean, stdev)
  return probabilities

测试calculateClassProbabilities()函数。


summaries = {0:[(1, 0.5)], 1:[(20, 5.0)]}
inputVector = [1.1, '?']
probabilities = calculateClassProbabilities(summaries, inputVector)
print('Probabilities for each class: {0}').format(probabilities)

运行测试,你会看到如下结果:

Probabilities for each class: {0: 0.7820853879509118, 1: 6.298736258150442e-05}

单一预测

既然可以计算一个数据样本属于每个类的概率,那么我们可以找到最大的概率值,并返回关联的类。

下面的predict()函数可以完成以上任务。


def predict(summaries, inputVector):
  probabilities = calculateClassProbabilities(summaries, inputVector)
  bestLabel, bestProb = None, -1
  for classValue, probability in probabilities.iteritems():
    if bestLabel is None or probability > bestProb:
      bestProb = probability
      bestLabel = classValue
  return bestLabel

测试predict()函数如下:

summaries = {'A':[(1, 0.5)], 'B':[(20, 5.0)]}
inputVector = [1.1, '?']
result = predict(summaries, inputVector)
print('Prediction: {0}').format(result)

运行测试,你会得到如下结果:

Prediction: A

多重预测

最后,通过对测试数据集中每个数据样本的预测,我们可以评估模型精度。getPredictions()函数可以实现这个功能,并返回每个测试样本的预测列表。


def getPredictions(summaries, testSet):
  predictions = []
  for i in range(len(testSet)):
    result = predict(summaries, testSet[i])
    predictions.append(result)
  return predictions

测试getPredictions()函数如下。


summaries = {'A':[(1, 0.5)], 'B':[(20, 5.0)]}
testSet = [[1.1, '?'], [19.1, '?']]
predictions = getPredictions(summaries, testSet)
print('Predictions: {0}').format(predictions)

运行测试,你会看到如下结果:


Predictions: ['A', 'B']

计算精度

预测值和测试数据集中的类别值进行比较,可以计算得到一个介于0%~100%精确率作为分类的精确度。getAccuracy()函数可以计算出这个精确率。


def getAccuracy(testSet, predictions):
  correct = 0
  for x in range(len(testSet)):
    if testSet[x][-1] == predictions[x]:
      correct += 1
  return (correct/float(len(testSet))) * 100.0

我们可以使用如下简单的代码来测试getAccuracy()函数。


testSet = [[1,1,1,'a'], [2,2,2,'a'], [3,3,3,'b']]
predictions = ['a', 'a', 'a']
accuracy = getAccuracy(testSet, predictions)
print('Accuracy: {0}').format(accuracy)

运行测试,你会得到如下结果:

Accuracy: 66.6666666667

合并代码

最后,我们需要将代码连贯起来。

下面是朴素贝叶斯Python版的逐步实现的全部代码。

# Example of Naive Bayes implemented from Scratch in Python
import csv
import random
import math

def loadCsv(filename):
  lines = csv.reader(open(filename, "rb"))
  dataset = list(lines)
  for i in range(len(dataset)):
    dataset[i] = [float(x) for x in dataset[i]]
  return dataset

def splitDataset(dataset, splitRatio):
  trainSize = int(len(dataset) * splitRatio)
  trainSet = []
  copy = list(dataset)
  while len(trainSet) < trainSize:
    index = random.randrange(len(copy))
    trainSet.append(copy.pop(index))
  return [trainSet, copy]

def separateByClass(dataset):
  separated = {}
  for i in range(len(dataset)):
    vector = dataset[i]
    if (vector[-1] not in separated):
      separated[vector[-1]] = []
    separated[vector[-1]].append(vector)
  return separated

def mean(numbers):
  return sum(numbers)/float(len(numbers))

def stdev(numbers):
  avg = mean(numbers)
  variance = sum([pow(x-avg,2) for x in numbers])/float(len(numbers)-1)
  return math.sqrt(variance)

def summarize(dataset):
  summaries = [(mean(attribute), stdev(attribute)) for attribute in zip(*dataset)]
  del summaries[-1]
  return summaries

def summarizeByClass(dataset):
  separated = separateByClass(dataset)
  summaries = {}
  for classValue, instances in separated.iteritems():
    summaries[classValue] = summarize(instances)
  return summaries

def calculateProbability(x, mean, stdev):
  exponent = math.exp(-(math.pow(x-mean,2)/(2*math.pow(stdev,2))))
  return (1 / (math.sqrt(2*math.pi) * stdev)) * exponent

def calculateClassProbabilities(summaries, inputVector):
  probabilities = {}
  for classValue, classSummaries in summaries.iteritems():
    probabilities[classValue] = 1
    for i in range(len(classSummaries)):
      mean, stdev = classSummaries[i]
      x = inputVector[i]
      probabilities[classValue] *= calculateProbability(x, mean, stdev)
  return probabilities

def predict(summaries, inputVector):
  probabilities = calculateClassProbabilities(summaries, inputVector)
  bestLabel, bestProb = None, -1
  for classValue, probability in probabilities.iteritems():
    if bestLabel is None or probability > bestProb:
      bestProb = probability
      bestLabel = classValue
  return bestLabel

def getPredictions(summaries, testSet):
  predictions = []
  for i in range(len(testSet)):
    result = predict(summaries, testSet[i])
    predictions.append(result)
  return predictions

def getAccuracy(testSet, predictions):
  correct = 0
  for i in range(len(testSet)):
    if testSet[i][-1] == predictions[i]:
      correct += 1
  return (correct/float(len(testSet))) * 100.0

def main():
  filename = 'pima-indians-diabetes.data.csv'
  splitRatio = 0.67
  dataset = loadCsv(filename)
  trainingSet, testSet = splitDataset(dataset, splitRatio)
  print('Split {0} rows into train={1} and test={2} rows').format(len(dataset), len(trainingSet), len(testSet))
  # prepare model
  summaries = summarizeByClass(trainingSet)
  # test model
  predictions = getPredictions(summaries, testSet)
  accuracy = getAccuracy(testSet, predictions)
  print('Accuracy: {0}%').format(accuracy)

main()

运行示例,得到如下输出:

Split 768 rows into train=514 and test=254 rows
Accuracy: 76.3779527559%
(0)

相关推荐

  • Python实现抓取城市的PM2.5浓度和排名

    主机环境:(Python2.7.9 / Win8_64 / bs4) 利用BeautifulSoup4来抓取 www.pm25.com 上的PM2.5数据,之所以抓取这个网站,是因为上面有城市PM2.5浓度排名(其实真正的原因是,它是百度搜PM2.5出来的第一个网站!) 程序里只对比了两个城市,所以多线程的速度提升并不是很明显,大家可以弄10个城市并开10个线程试试. 最后吐槽一下:上海的空气质量怎么这么差!!! PM25.py 复制代码 代码如下: #!/usr/bin/env python

  • 朴素贝叶斯算法的python实现方法

    本文实例讲述了朴素贝叶斯算法的python实现方法.分享给大家供大家参考.具体实现方法如下: 朴素贝叶斯算法优缺点 优点:在数据较少的情况下依然有效,可以处理多类别问题 缺点:对输入数据的准备方式敏感 适用数据类型:标称型数据 算法思想: 比如我们想判断一个邮件是不是垃圾邮件,那么我们知道的是这个邮件中的词的分布,那么我们还要知道:垃圾邮件中某些词的出现是多少,就可以利用贝叶斯定理得到. 朴素贝叶斯分类器中的一个假设是:每个特征同等重要 函数 loadDataSet() 创建数据集,这里的数据集

  • python执行外部程序的常用方法小结

    本文实例总结了python执行外部程序的常用方法.分享给大家供大家参考.具体分析如下: 在python中我们可以通过下面的方法直接调用系统命令或者外部程序,使用方便 1.os模块的execl方法 Python的execl系统方法同Unix的exec系统调用是一致的.这些方法适用于在子进程中调用外部程序的情况,因为外部程序会替换当前进程的代码,不会返回. 也就是说,这个shell进程被占领,将执行第一个execl的命令程序而不再返回. 2.使用os模块的system方法 system方法会创建子进

  • 用Python从零实现贝叶斯分类器的机器学习的教程

    朴素贝叶斯算法简单高效,在处理分类问题上,是应该首先考虑的方法之一. 通过本教程,你将学到朴素贝叶斯算法的原理和Python版本的逐步实现. 更新:查看后续的关于朴素贝叶斯使用技巧的文章"Better Naive Bayes: 12 Tips To Get The Most From The Naive Bayes Algorithm" 朴素贝叶斯分类器,Matt Buck保留部分版权 关于朴素贝叶斯 朴素贝叶斯算法是一个直观的方法,使用每个属性归属于某个类的概率来做预测.你可以使用这

  • Python实现的朴素贝叶斯分类器示例

    本文实例讲述了Python实现的朴素贝叶斯分类器.分享给大家供大家参考,具体如下: 因工作中需要,自己写了一个朴素贝叶斯分类器. 对于未出现的属性,采取了拉普拉斯平滑,避免未出现的属性的概率为零导致整个条件概率都为零的情况出现. 朴素贝叶斯的基本原理网上很容易查到,这里不再叙述,直接附上代码 因工作中需要,自己写了一个朴素贝叶斯分类器.对于未出现的属性,采取了拉普拉斯平滑,避免未出现的属性的概率为零导致整个条件概率都为零的情况出现. class NBClassify(object): def _

  • Python实现朴素贝叶斯分类器的方法详解

    本文实例讲述了Python实现朴素贝叶斯分类器的方法.分享给大家供大家参考,具体如下: 贝叶斯定理 贝叶斯定理是通过对观测值概率分布的主观判断(即先验概率)进行修正的定理,在概率论中具有重要地位. 先验概率分布(边缘概率)是指基于主观判断而非样本分布的概率分布,后验概率(条件概率)是根据样本分布和未知参数的先验概率分布求得的条件概率分布. 贝叶斯公式: P(A∩B) = P(A)*P(B|A) = P(B)*P(A|B) 变形得: P(A|B)=P(B|A)*P(A)/P(B) 其中 P(A)是

  • C++学习贝叶斯分类器实现手写数字识别示例解析

    大家好啊!这次的文章是上一个文章的后续,与上一次不同的是,这一次对数字识别采用的是贝叶斯(Bayes)分类器.贝叶斯在概率论与数理统计这门课讲过,下面我们简单了解一下: 首先,贝叶斯公式是 具体的解释就不说了,我们说一说把贝叶斯用在数字识别的什么位置.除了识别部分,其他的包括遍历文件夹和图片数字化都不变:0到9共十个数,所以分母有十项,P(Bj)(j是下标)相应的是0到9,则每一个的概率是1/10,分子上的P(Bi)是取到0到9中的一个,所以概率也是1/10. (小伙伴如果看不明白建议去看看贝叶

  • Python单元测试_使用装饰器实现测试跳过和预期故障的方法

    Python单元测试unittest中提供了一下四种装饰器实现测试跳过和预期故障.(使用Python 2.7.13) 请查考Python手册中: https://docs.python.org/dev/library/unittest.html The following decorators implement test skipping and expected failures: #以下装饰器实施测试跳过和预期故障: @unittest.skip(原因) Unconditionally s

  • Python深入学习之装饰器

    装饰器(decorator)是一种高级Python语法.装饰器可以对一个函数.方法或者类进行加工.在Python中,我们有多种方法对函数和类进行加工,比如在Python闭包中,我们见到函数对象作为某一个函数的返回结果.相对于其它方式,装饰器语法简单,代码可读性高.因此,装饰器在Python项目中有广泛的应用. 装饰器最早在Python 2.5中出现,它最初被用于加工函数和方法这样的可调用对象(callable object,这样的对象定义有__call__方法).在Python 2.6以及之后的

  • 深入解析Python中的descriptor描述器的作用及用法

    一般来说,一个描述器是一个有"绑定行为"的对象属性(object attribute),它的访问控制被描述器协议方法重写.这些方法是 __get__(), __set__(), 和 __delete__() .有这些方法的对象叫做描述器. 默认对属性的访问控制是从对象的字典里面(__dict__)中获取(get), 设置(set)和删除(delete)它.举例来说, a.x 的查找顺序是, a.__dict__['x'] , 然后 type(a).__dict__['x'] , 然后找

  • Python实现的RSS阅读器实例

    本文实例讲述了Python实现的RSS阅读器.分享给大家供大家参考.具体如下: # -*- coding:utf-8 -*- # file: pyRSS.py # import Tkinter import urllib import xml.parsers.expat class MyXML: # XML解析类 def __init__(self, edit): self.parser = xml.parsers.expat.ParserCreate() # 生成XMLParser self.

  • 12步入门Python中的decorator装饰器使用方法

    装饰器(decorator)是一种高级Python语法.装饰器可以对一个函数.方法或者类进行加工.在Python中,我们有多种方法对函数和类进行加工,比如在Python闭包中,我们见到函数对象作为某一个函数的返回结果.相对于其它方式,装饰器语法简单,代码可读性高.因此,装饰器在Python项目中有广泛的应用. 装饰器最早在Python 2.5中出现,它最初被用于加工函数和方法这样的可调用对象(callable object,这样的对象定义有call方法).在Python 2.6以及之后的Pyth

  • python实现12306火车票查询器

    12306火车票购票软件大家都用过,怎么用Python写一个命令行的火车票查看器,要求在命令行敲一行命令来获得你想要的火车票信息,下面通过本文学习吧. Python火车票查询器 接口设置 先给这个小应用起个名字吧,既然及查询票务信息,那就叫它tickets 我们希望用户只要输入出发站,到达站以及日期就让就能获得想要的信息,所以tickets应该这样被使用: $ tickets from to date 最终 $ tickets [-gdtkz] from to date 开发环境 用virtua

随机推荐