英文名为Support Vector Machine简称为SVM,是一种二分类模型
线性可分支持向量机:如下图就可以通过一条红色的直线将蓝色的球和红色的球完全区分开,该直线被称为线性分类器,如果是高维的,就可以通过一个超平面将三维立体空间里的样本点给分开。通过硬间隔最大化,学习一个线性分类器。





几何间隔

以上公式的来源:假如有一个点(A,B)到直线方程为ax+by+c=0的距离如下图所示

如果将直线方程改为w0x0+w1x1+b=0,那么点到直线的距离就可以改为如下图所示

再进一步化简即可得到

如果当前的样本点属于正样本点,则其y的值为1,反之y的值为-1,所以为了去掉距离公式中的绝对值,可以再前面乘以个y的值,因为如果为负样本,距离值为负,y的值也是负,这样得到的乘积也依然为正,和以前没有乘以y,然后加绝对值得到的结果一致
由以上步骤就可以得到几何间隔的公式
函数间隔

与几何间隔相比较一个最大的区别就是没有了分母的||W||,这样的话,就可以通过缩放方程的系数来更改函数间隔的值,同样也就可以通过缩放系数值,来使得函数间隔的值设置为1,举个简单的例子假设直线方程为2x+y+1=0,样本点的坐标为(1,2),则不难算出集合间隔点到直线的距离为5/根号5,而函数间隔为5;如果将直线方程的系数扩大2倍,也就是变为4x+2*y+2=0,此时算出来的几何间隔的值任然不变,但是函数间隔的值变为10,也就是说通过缩放方程系数的值,可以改变函数间隔的值,而不能改变几何间隔的值
两者的关系


最大间隔分离超平面


由目标函数构造拉格朗日函数,下图的变量写错了应该是L(w,b,a)

然后对拉格朗日求解



求解步骤如下图:




由上图可知得到的结果与线性可分向量机得到的结果可以说是完全一样,唯一的区别就是约束条件不太一样


由上图可知在原空间中,用一个椭圆曲线将圆点和xx分开,总所周知,椭圆的方程为x^2 / a^2 + y^2 / b^2,为了使得其与机器学习相关,可以再左右添加一个参数c,然后用w1表示1/c* a^2 , w2表示1 / c*b^2, b表示-c,则椭圆方程可以简化为w1 * x1^2 + w2 * x2^2 + b=0,坐在新空间中存在一种对应关系,使得z1= x1^2 ,z2= x2^2 ,则椭圆方程可以进一步简化为w1 * Z1+ w2 * Z2+b=0,这个过程也被称为核技巧,也就是将元空间的曲线方程,转化为新空间的线性可分的直线方程
由下图可知,当o(x)取不同维数的函数,经过映射之后得到的核函数都是一样的。所以核函数的原理就是通过在低维空间的计算,而去完成在高维空间所能完成的事情



from numpy import *
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
def loadDataSet(fileName):
dataMat=[];labelMat=[]#数据集以及标签集分开存储
fr=open(fileName)
for line in fr.readlines():#读取文件的每一行数据
lineArr=line.strip().split('\t')#每个数据以空格分开
dataMat.append([float(lineArr[0]),float(lineArr[1])])#将每一行的第一个数据和第二个数据存放在数据集中
labelMat.append(float(lineArr[2]))#每一行的第三个数据存放在标签集中
return dataMat,labelMat
#alpha的选取,随机选择一个不等于i值得j
def selectJrand(i,m):#i的值就是当前选定的alpha的值
j=i
while(j==i):
j=int(random.uniform(0,m))
return j
#进行剪辑
def clipAlpha(aj,H,L):
if aj>H:
aj=H
if L>aj:
aj=L
return aj
#dataMatIn就是之前讲的公式里的x,classLabels就是之前公式里的y
#toler误差值达到多少时可以停止,maxIter迭代次数达到多少是可以停止
def smoSimple(dataMatIn,classLabels,C,toler,maxIter):
dataMatrix=mat(dataMatIn);labelMat=mat(classLabels).transpose()
#初始化b为0
b=0
#获取数据维度
m,n=shape(dataMatrix)
#初始化所有alpha为0
alphas=mat(zeros((m,1)))
iter=0
#迭代求解
while(iter<maxIter):
alphaPairsChanged=0
for i in range(m):
#计算g(xi)
gXi= float(multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[i,:].T))+b
#计算Ei
Ei=gXi-float(labelMat[i])
if((labelMat[i]*Ei<-toler) and (alphas[i]<C)) or ((labelMat[i]*Ei>toler) and (alphas[i]>0)):
#随机选择一个待优化的alpha(先随机出alpha下标)
j=selectJrand(i,m)
#计算g(xj)
gXj = float(multiply(alphas, labelMat).T * (dataMatrix * dataMatrix[j, :].T)) + b
#计算Ej
Ej = gXj - float(labelMat[j])
#把原来的恶alpha的值复制一份,作为old的值
alphaIold=alphas[i].copy();alpaJold=alphas[j].copy()
#计算上下界
if(labelMat[i]!=labelMat[j]):
L=max(0,alphas[j]-alphas[i])
H=min(C,C+alphas[j]-alphas[i])
else:
L=max(0,alphas[j]+alphas[i]-C)
H=min(C,alphas[j]-alphas[i])
if L==H:
print("L==H")
continue
#计算eta:在公式里就是计算K11+K22-2K12,但是这里算的负的eta
eta=2*dataMatrix[i,:]*dataMatrix[j,:].T-dataMatrix[i,:]*dataMatrix[i,:].T-dataMatrix[j,:]*dataMatrix[j,:].T
if eta>=0:
print("eta>=0")
continue
#计算alpha[j],为了和公式对应把j看出2
alphas[j]-=labelMat[j]*(Ei-Ej)/eta
#剪辑alphas[j],为了和公式对应把j看成2
alphas[j]=clipAlpha(alphas[j],H,L)
if(abs(alphas[j]-alpaJold)<0.00001):
print("j not moving enough")
continue
#计算alphas[i],为了和公式对应把i看成1
alphas[i] += labelMat[i]*labelMat[j]*(alpaJold-alphas[j])
#计算b1
b1=-Ei-labelMat[i]*(dataMatrix[i,:]*dataMatrix[i,:].T)*(alphas[i]-alphaIold)-labelMat[j]*(dataMatrix[j,:]*dataMatrix[i,:].T)*(alphas[j]-alpaJold)+b
#计算b2
b2=-Ej-labelMat[i]*(dataMatrix[i,:]*dataMatrix[j,:].T)*(alphas[i]-alphaIold)-labelMat[j]*(dataMatrix[j,:]*dataMatrix[j,:].T)*(alphas[j]-alpaJold)+b
#求解b
if(0<alphas[i]) and (C>alphas[j]):
b = b1
elif (0<alphas[j]) and (C>alphas[j]):
b = b2
else:
b=(b1+b2)/2.0
alphaPairsChanged+=1
print("iter:%d i:%d,pairs changed %d" %(iter,i,alphaPairsChanged))
if(alphaPairsChanged==0):
iter+=1
else:
iter=0
print("iteration number:%d" %iter)
return b,alphas
#计算w的值
def calcWs(dataMat, labelMat, alphas):
X=mat(dataMat);labelMat=mat(labelMat).transpose()
m,n=shape(X)
#初始化w都为1
w=zeros((n,1))
#循环计算
for i in range(m):
w+=multiply(alphas[i]*labelMat[i],X[i,:].T)
return w
#画图
def showClassifer(dataMat, labelMat, b,alphas,w):
fig=plt.figure()
ax=fig.add_subplot(111)
cm_dark=mpl.colors.ListedColormap(['g','r'])
ax.scatter(array(dataMat)[:,0],array(dataMat)[:,1],c=array(labelMat).squeeze(),cmap=cm_dark,s=30)
#画决策平面
x=arange(-2.0,12.0,0.1)
y=(-w[0]*x-b)/w[1]
ax.plot(x,y.reshape(-1,1))
ax.axis([-2,12,-8,6])
#画支持向量
alphas_non_zeros_index=where(alphas>0)
for i in alphas_non_zeros_index[0]:
circle= Circle((dataMat[i][0],dataMat[i][1]),0.2,facecolor='none',edgecolor=(0,0.8,0.8),linewidth=3,alpha=0.5)
ax.add_patch(circle)
plt.show()
if __name__ == '__main__':
dataMat, labelMat = loadDataSet('testSet.txt')
b,alphas=smoSimple(dataMat,labelMat,0.6,0.001,40)
w = calcWs(dataMat, labelMat, alphas)
showClassifer(dataMat, labelMat, b,alphas,w)
画决策平面
x=arange(-2.0,12.0,0.1)
y=(-w[0]*x-b)/w[1]
ax.plot(x,y.reshape(-1,1))
ax.axis([-2,12,-8,6])
plt.show()

画支持向量
alphas_non_zeros_index=where(alphas>0)
for i in alphas_non_zeros_index[0]:
circle= Circle((dataMat[i][0],dataMat[i][1]),0.2,facecolor='none',edgecolor=(0,0.8,0.8),linewidth=3,alpha=0.5)
ax.add_patch(circle)
plt.show()

from numpy import *
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
class optStruct:
"""
数据结构,维护所有需要操作的值
Parameters:
dataMatIn - 数据矩阵
classLabels - 数据标签
C - 松弛变量
toler - 容错率
"""
def __init__(self, dataMatIn, classLabels, C, toler, kTup):
self.X = dataMatIn #数据矩阵
self.labelMat = classLabels #数据标签
self.C = C #松弛变量
self.tol = toler #容错率
self.m = shape(dataMatIn)[0] #数据矩阵行数
self.alphas = mat(zeros((self.m,1))) #根据矩阵行数初始化alpha参数为0
self.b = 0 #初始化b参数为0
self.eCache = mat(zeros((self.m,2))) #根据矩阵行数初始化虎误差缓存,第一列为是否有效的标志位,第二列为实际的误差E的值。
self.K = mat(zeros((self.m, self.m))) # 初始化核K
for i in range(self.m): # 计算所有数据的核K
self.K[:, i] = kernelTrans(self.X, self.X[i, :], kTup)
def loadDataSet(fileName):
dataMat=[];labelMat=[]#数据集以及标签集分开存储
fr=open(fileName)
for line in fr.readlines():#读取文件的每一行数据
lineArr=line.strip().split('\t')#每个数据以空格分开
dataMat.append([float(lineArr[0]),float(lineArr[1])])#将每一行的第一个数据和第二个数据存放在数据集中
labelMat.append(float(lineArr[2]))#每一行的第三个数据存放在标签集中
return dataMat,labelMat
def calcEk(oS, k):
"""
计算误差
Parameters:
oS - 数据结构
k - 标号为k的数据
Returns:
Ek - 标号为k的数据误差
"""
fXk = float(multiply(oS.alphas, oS.labelMat).T * oS.K[:, k] + oS.b)
Ek = fXk - float(oS.labelMat[k])
return Ek
def selectJ(i, oS, Ei):
"""
内循环启发方式2
Parameters:
i - 标号为i的数据的索引值
oS - 数据结构
Ei - 标号为i的数据误差
Returns:
j, maxK - 标号为j或maxK的数据的索引值
Ej - 标号为j的数据误差
"""
maxK = -1; maxDeltaE = 0; Ej = 0 #初始化
oS.eCache[i] = [1,Ei] #设为有效 #根据Ei更新误差缓存
validEcacheList = nonzero(oS.eCache[:,0].A)[0] #返回误差不为0的数据的索引值
if (len(validEcacheList)) > 1: #有不为0的误差
for k in validEcacheList: #迭代所有有效的缓存,找到误差最大的E
if k == i: continue #不计算i,浪费时间
Ek = calcEk(oS, k) #计算Ek
deltaE = abs(Ei - Ek) #计算|Ei-Ek|
if (deltaE > maxDeltaE): #找到maxDeltaE
maxK = k; maxDeltaE = deltaE; Ej = Ek
return maxK, Ej #返回maxK,Ej
else: #没有不为0的误差
j = selectJrand(i, oS.m) #随机选择alpha_j的索引值
Ej = calcEk(oS, j) #计算Ej
return j, Ej
#跟新缓存
def updateEk(oS, k):
"""
计算Ek,并更新误差缓存
Parameters:
oS - 数据结构
k - 标号为k的数据的索引值
Returns:
无
"""
Ek = calcEk(oS, k) #计算Ek
oS.eCache[k] = [1,Ek] #更新误差缓存
def innerL(i,oS):
"""
优化的SMO算法
Parameters:
i - 标号为i的数据的索引值
oS - 数据结构
Returns:
1 - 有任意一对alpha值发生变化
0 - 没有任意一对alpha值发生变化或变化太小
"""
# 步骤1:计算误差Ei
Ei = calcEk(oS, i)
# 优化alpha,设定一定的容错率。
if ((oS.labelMat[i] * Ei < -oS.tol) and (oS.alphas[i] < oS.C)) or (
(oS.labelMat[i] * Ei > oS.tol) and (oS.alphas[i] > 0)):
# 使用内循环启发方式2选择alpha_j,并计算Ej
j, Ej = selectJ(i, oS, Ei)
# 保存更新前的aplpha值,使用深拷贝
alphaIold = oS.alphas[i].copy()
alphaJold = oS.alphas[j].copy()
# 步骤2:计算上下界L和H
if (oS.labelMat[i] != oS.labelMat[j]):
L = max(0, oS.alphas[j] - oS.alphas[i])
H = min(oS.C, oS.C + oS.alphas[j] - oS.alphas[i])
else:
L = max(0, oS.alphas[j] + oS.alphas[i] - oS.C)
H = min(oS.C, oS.alphas[j] + oS.alphas[i])
if L == H:
print("L==H")
return 0
# 步骤3:计算eta
eta = 2.0 * oS.K[i, j] - oS.K[i, i] - oS.K[j, j]
if eta >= 0:
print("eta>=0")
return 0
# 步骤4:更新alpha_j
oS.alphas[j] -= oS.labelMat[j] * (Ei - Ej) / eta
# 步骤5:修剪alpha_j
oS.alphas[j] = clipAlpha(oS.alphas[j], H, L)
# 更新Ej至误差缓存
updateEk(oS, j)
if (abs(oS.alphas[j] - alphaJold) < 0.00001):
print("alpha_j变化太小")
return 0
# 步骤6:更新alpha_i
oS.alphas[i] += oS.labelMat[j] * oS.labelMat[i] * (alphaJold - oS.alphas[j])
# 更新Ei至误差缓存
updateEk(oS, i)
# 步骤7:更新b_1和b_2
b1 = oS.b - Ei - oS.labelMat[i] * (oS.alphas[i] - alphaIold) * oS.K[i, i] - oS.labelMat[j] * (
oS.alphas[j] - alphaJold) * oS.K[i, j]
b2 = oS.b - Ej - oS.labelMat[i] * (oS.alphas[i] - alphaIold) * oS.K[i, j] - oS.labelMat[j] * (
oS.alphas[j] - alphaJold) * oS.K[j, j]
# 步骤8:根据b_1和b_2更新b
if (0 < oS.alphas[i]) and (oS.C > oS.alphas[i]):
oS.b = b1
elif (0 < oS.alphas[j]) and (oS.C > oS.alphas[j]):
oS.b = b2
else:
oS.b = (b1 + b2) / 2.0
return 1
else:
return 0
#alpha的选取,随机选择一个不等于i值得j
def selectJrand(i,m):#i的值就是当前选定的alpha的值
j=i
while(j==i):
j=int(random.uniform(0,m))
return j
#进行剪辑
# aj - alpha值
# H - alpha上限
# L - alpha下限
def clipAlpha(aj,H,L):
if aj>H:
aj=H
if L>aj:
aj=L
return aj
#dataMatIn就是之前讲的公式里的x,classLabels就是之前公式里的y
#toler误差值达到多少时可以停止,maxIter迭代次数达到多少是可以停止
def smoSimple(dataMatIn,classLabels,C,toler,maxIter,kTup = ('lin',0)):
"""
完整的线性SMO算法
Parameters:
dataMatIn - 数据矩阵
classLabels - 数据标签
C - 松弛变量
toler - 容错率
maxIter - 最大迭代次数
kTup - 包含核函数信息的元组
Returns:
oS.b - SMO算法计算的b
oS.alphas - SMO算法计算的alphas
"""
oS = optStruct(mat(dataMatIn), mat(classLabels).transpose(), C, toler, kTup) #初始化数据结构iter = 0 # 初始化当前迭代次数
iter=0
entireSet = True
alphaPairsChanged = 0
while (iter < maxIter) and ((alphaPairsChanged > 0) or (entireSet)): # 遍历整个数据集都alpha也没有更新或者超过最大迭代次数,则退出循环
alphaPairsChanged = 0
if entireSet: # 遍历整个数据集
for i in range(oS.m):
alphaPairsChanged += innerL(i, oS) # 使用优化的SMO算法
print("全样本遍历:第%d次迭代 样本:%d, alpha优化次数:%d" % (iter, i, alphaPairsChanged))
iter += 1
else: # 遍历非边界值
nonBoundIs = nonzero((oS.alphas.A > 0) * (oS.alphas.A < C))[0] # 遍历不在边界0和C的alpha
for i in nonBoundIs:
alphaPairsChanged += innerL(i, oS)
print("非边界遍历:第%d次迭代 样本:%d, alpha优化次数:%d" % (iter, i, alphaPairsChanged))
iter += 1
if entireSet: # 遍历一次后改为非边界遍历
entireSet = False
elif (alphaPairsChanged == 0): # 如果alpha没有更新,计算全样本遍历
entireSet = True
print("迭代次数: %d" % iter)
return oS.b, oS.alphas
#计算w的值
def calcWs(dataMat, labelMat, alphas):
X=mat(dataMat);labelMat=mat(labelMat).transpose()
m,n=shape(X)
#初始化w都为1
w=zeros((n,1))
#循环计算
for i in range(m):
w+=multiply(alphas[i]*labelMat[i],X[i,:].T)
return w
#核函数
def kernelTrans(X,A,kTup):
m,n=shape(X)
K=mat(zeros((m,1)))
if kTup[0]=='lin':#线性核
K=X*A.T
elif kTup[0] == 'rbf':#高斯核
for j in range(m):
deltaRow = X[j,:]-A
K[j]=deltaRow*deltaRow.T
K = exp(K/(-2*kTup[1]**2))
else:
raise NameError("Houston we Have a Problem--\ That Kernel is not recognized")
return K
#画图
def showClassifer(dataMat, labelMat, b,alphas,w):
fig=plt.figure()
ax=fig.add_subplot(111)
cm_dark=mpl.colors.ListedColormap(['g','r'])
ax.scatter(array(dataMat)[:,0],array(dataMat)[:,1],c=array(labelMat).squeeze(),cmap=cm_dark,s=30)
#画决策平面
# x=arange(-2.0,12.0,0.1)
# y=(-w[0]*x-b)/w[1]
# ax.plot(x,y.reshape(-1,1))
# ax.axis([-2,12,-8,6])
#画支持向量
alphas_non_zeros_index=where(alphas>0)
for i in alphas_non_zeros_index[0]:
circle= Circle((dataMat[i][0],dataMat[i][1]),0.03,facecolor='none',edgecolor=(0,0.8,0.8),linewidth=3,alpha=0.5)
ax.add_patch(circle)
plt.show()
if __name__ == '__main__':
dataMat, labelMat = loadDataSet('testSetRBF.txt')
b,alphas=smoSimple(dataMat,labelMat,0.6,0.001,40)
w = calcWs(dataMat, labelMat, alphas)
showClassifer(dataMat, labelMat, b,alphas,w)
运行结果

这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub
目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称
最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总
深度学习部署:Windows安装pycocotools报错解决方法1.pycocotools库的简介2.pycocotools安装的坑3.解决办法更多Ai资讯:公主号AiCharm本系列是作者在跑一些深度学习实例时,遇到的各种各样的问题及解决办法,希望能够帮助到大家。ERROR:Commanderroredoutwithexitstatus1:'D:\Anaconda3\python.exe'-u-c'importsys,setuptools,tokenize;sys.argv[0]='"'"'C:\\Users\\46653\\AppData\\Local\\Temp\\pip-instal
@作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors 1、什么是behaviors 2、behaviors的工作方式 3、创建behavior 4、导入并使用behavior 5、behavior中所有可用的节点 6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors 1、什么是behaviorsbehaviors是小程序中,用于实现
require"socket"server="irc.rizon.net"port="6667"nick="RubyIRCBot"channel="#0x40"s=TCPSocket.open(server,port)s.print("USERTesting",0)s.print("NICK#{nick}",0)s.print("JOIN#{channel}",0)这个IRC机器人没有连接到IRC服务器,我做错了什么? 最佳答案 失败并显示此消息::irc.shakeababy.net461*USER:Notenoughparame
我完全不是程序员,正在学习使用Ruby和Rails框架进行编程。我目前正在使用Ruby1.8.7和Rails3.0.3,但我想知道我是否应该升级到Ruby1.9,因为我真的没有任何升级的“遗留”成本。缺点是什么?我是否会遇到与普通gem的兼容性问题,或者甚至其他我不太了解甚至无法预料的问题? 最佳答案 你应该升级。不要坚持从1.8.7开始。如果您发现不支持1.9.2的gem,请避免使用它们(因为它们很可能不被维护)。如果您对gem是否兼容1.9.2有任何疑问,您可以在以下位置查看:http://www.railsplugins.or
如何学习ruby的正则表达式?(对于假人) 最佳答案 http://www.rubular.com/在Ruby中使用正则表达式时是一个很棒的工具,因为它可以立即将结果可视化。 关于ruby-我如何学习ruby的正则表达式?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/1881231/
我已经按照https://github.com/wayneeseguin/rvm#installation上的说明通过RVM安装了Ruby.有关信息,我有所有文件(readline-5.2.tar.gz、readline-6.2.tar.gz、ruby-1.9.3-p327.tar.bz2、rubygems-1.8.24.tgz、wayneeseguin-rvm-stable.tgz和yaml-0.1.4.tar.gz)在~/.rvm/archives目录中,我不想在任何目录中重新下载它们方式。当我这样做时:sudo/usr/bin/apt-getinstallbuild-essent
我的Ruby-on-Rails项目中有以下文件结构,用于规范:/spec/msd/serviceservice_spec.rb/support/my_modulerequests_stubs.rb我的request_stubs.rb有:moduleMyModule::RequestsStubsmodule_functiondeflist_clientsurl="dummysite.com/clients"stub_request(:get,url).to_return(status:200,body:"clientsbody")endend在我的service_spec.rb我有:re