Scikit-learn-python机器学习工具入门学习
/scikit-learn/scikit-learn
官网:http://scikit-learn.org/stable/
参考官网文档,需要numpy、scipy,我直接尝试在文件目录下
sudo python setup.py install出现错误,提示如下:
&&& import sklearn
Traceback (most recent call last):
File &&stdin&&, line 1, in &module&
File &sklearn/__init__.py&, line 37, in &module&
from . import __check_build
File &sklearn/__check_build/__init__.py&, line 46, in &module&
raise_build_error(e)
File &sklearn/__check_build/__init__.py&, line 41, in raise_build_error
%s&&& % (e, local_dir, ''.join(dir_content).strip(), msg))
ImportError: No module named _check_build
___________________________________________________________________________
Contents of sklearn/__check_build:
__init__.py
__init__.pyc
_check_build.c
_check_build.pyx
___________________________________________________________________________
It seems that scikit-learn has not been built correctly.
If you have installed scikit-learn from source, please do not forget
to build the package before using it: run `python setup.py install` or
`make` in the source directory.
If you have used an installer, please check that it is suited for your
Python version, your operating system and your platform.
尝试着重新安装numpy scipy 才发现Mac系统自己已经自带了许多类库了,如下:
CoreGraphics/
Twisted-12.2.0-py2.7.egg-info/
altgraph-0.10.1-py2.7.egg-info/
bdist_mpkg/
bdist_mpkg-0.4.4-py2.7.egg-info/
macholib-1.5-py2.7.egg-info/
matplotlib/
modulegraph/
modulegraph-0.10.1-py2.7.egg-info/
mpl_toolkits/
py2app-0.7.1-py2.7.egg-info/
python_dateutil-1.5-py2.7.egg-info/
pytz-2012d-py2.7.egg-info/
setuptools/
setuptools-0.6c12dev_r88846-py2.7.egg-info/
xattr-0.6.4-py2.7.egg-info/
zope.interface-3.8.0-py2.7.egg-info/
后来尝试了好几种方法,使用pip和easy_install的方法,分别报错。我就在site-packages下删除了原来的文件,然后重新安装了,就成功了。(刚开始失败的原因可能是没有把终端重启,重新进入python)
3、测试学习
Python 2.7.5 (default, Sep 12 :34)
[GCC 4.2.1 Compatible Apple LLVM 5.0 (clang-500.0.68)] on darwin
Type &help&, ©right&, &credits& or &license& for more information.
&&& import sklearn
&&& from sklearn import datasets
&&& iris = datasets.load_iris()
&&& digits = datasets.load_digits()
&&& print(digits.data)
4、后续计划
想跟着自带的例子,将机器学习的常用算法做一个后续的总结,是不错的学习资料。
http://scikit-learn.org/stable/auto_examples/feature_selection_pipeline.html
看过本文的人也看了:
我要留言技术领域:
取消收藏确定要取消收藏吗?
删除图谱提示你保存在该图谱下的知识内容也会被删除,建议你先将内容移到其他图谱中。你确定要删除知识图谱及其内容吗?
删除节点提示无法删除该知识节点,因该节点下仍保存有相关知识内容!
删除节点提示你确定要删除该知识节点吗?4080人阅读
Python(11)
需要的可直接转至成功安装流程
本人最近刚看到scikit-learn库,觉得在python中调用用来做些测试之类的非常方便,就开始配置,由于之前装了python2.7,就打算在之前的版本中配置,从网上各个帖子上尝试方法,最后终于提示安装成功,但是在运行import sklearn的时候总是报错。于是就开启了苦逼的python菜鸟学站之路。。。。。
关于各种修补的话,我参考的博文如下:
-----------------------------------------------------分割线-----------------------------------------------------------------
感谢以上各位博主的分享,但是,但是,,,,,,,,,,,,,尝试各种方法修补,还是运行不对不对,最后破釜沉舟,把所有的都卸载了重装。下面就把重新安装的过程记录一下,以方便有人也出现我之前困扰的盆友参考。
这次采用安装最新版的python3.5.1
关于python的基础安装可参考博客
python下载地址:
pip下载地址:
按照文中的步骤,是可以成功安装python的(照此法我试过,朋友也试过)。成功安装python之后,再开始进一步的配置。
<span style="color:#.&&&&&&&&&&&&&&关于numpy的安装,直接在cmd中pip
install numpy,即可成功安装numpy。
<span style="color:#.&&&&&&&&&&&&&&接下来pip install Djangan安装成功,这些都可以在Python3.5.1 Shell中测试是否成功。
<span style="color:#.&&&&&&&&&&&&&&安装scikit-learn。
耶!提示成功。赶紧你去测试一下是否能正确调用。
悲催。。。。。。。。。ImportError: No module named ‘scipy’。需要先安装scipy。
<span style="color:#.&&&&&&&&&&&&&&安装scipy。直接pip install scipy会报错,如下图所示。
分析错误原因,三cipy包包含有C代码,安装时需要根据所使用的操作系统进行编译,因此不同的操作系统的安装方式是不同的。下面将介绍在windows操作系统中,如何安装scipy包:
<span style="color:#)&&&&&&&在网页中&&下载对应操作系统的预编译安装包,需要根据python版本是<span style="color:#.x还是<span style="color:#.x,系统是<span style="color:#位还是<span style="color:#位进行选择
这里我下载的是scipy-0.17.0-cp35-none-win32.whl,并把下载好后的这个文件放到python安装目录下的Scipts文件夹下,我的本机的话就是F:\Python35\Scripts目录下。
<span style="color:#)&&&&&&&使用pip包管理器进行安装,在cmd中先进到存放scipy-0.17.0-cp35-none-win32.whl文件的目录中,输入
pipinstall 下载scipy安装包的路径(切记,一定要输入文件名啊,我可是又栽在这里了啊,又犯错一次,如下,请绕坑)
正确的为:
这时候终于把numpy和scipy都安装成功了,终于可以进行scikit-learn的安装了。
5.&&&&&&&&&&&&&&&&&&&&&&安装scikit-learn.
在编译器中运行importsklearn还是显示失败。
报错:ImportError:Dll load failed:找不到指定的模块,说明还是pip安装的不成功,那就直接去下载scikit-learn的安装包,手动安装试试。从以下网站下载
这里由于版本需要对应一致,我下载的是
scikit_learn-0.17.1-cp35-cp35m-win32.whl。把下载好后的这个文件放到python安装目录下的Scipts文件夹下,我的本机的话就是F:\Python35\Scripts目录下
在cmd中安装如下:
在编译器中import sklearn,再次失败
&&&import sklearn
Traceback(most recent call last):
& File &&pyshell#3&&, line 1,in &module&
&&& import sklearn
& File&F:\Python35\lib\site-packages\sklearn\__init__.py&, line 57, in&module&
&&& from .base import clone
& File&F:\Python35\lib\site-packages\sklearn\base.py&, line 9, in&module&
&&& from scipy import sparse
& File&F:\Python35\lib\site-packages\scipy\sparse\__init__.py&, line 214,in &module&
&&& from .csr import *
& File&F:\Python35\lib\site-packages\scipy\sparse\csr.py&, line 13, in&module&
&&& from ._sparsetools import csr_tocsc,csr_tobsr, csr_count_blocks, \
ImportError:DLL load failed: 找不到指定的模块。
再次查找原因,看到&integratesclassic machine learning algorithms.& Requires&.
安装numpy-1.10.4+mkl-cp35-cp35m-win32.whl后会有冲突
终于搞定了,用numpy-MKL替换numpy,,,,,,,结果还是不对滴。。。。。
此时import sklearn运行不对也就算了,纳尼from scipyimport linalg也会报错:
Traceback (most recent call last):
& File &&stdin&&, line 1, in &module&
AttributeError: 'module' object has
attribute 'linalg'
。。。。。。。。。实在是受不鸟咯。。。搜索下有的网友说是scipy的版本可能不支持最新的python3.5.1,,于是决定完全重新安装python2.7.11。有了之前的各种试错经验,这次一步步很小心的按照步骤依次安装
正确安装流程
依次下载如下所需文件
<span style="color:#、安装PYTHON2.7.11
<span style="color:#、安装pip-8.1.1
<span style="color:#、安装numpy (pip install numpy-1.10.4+mkl-cp27-cp27m-win32.whl)
<span style="color:#、安装scipy(pip install scipy-0.17.0-cp27-none-win32.whl)
<span style="color:#、安装matplotlib (pip install matplotlib-1.5.1-cp27-none-win32.whl)
<span style="color:#、安装scikit-learn (pip install scikit_learn-0.17.1-cp27-cp27m-win32.whl)
经过这两天的试错,经历了各种头大,感觉现在已经把各种流程都通下来了,再安装其他python相关的包库应该都不成问题啦
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:133037次
积分:2474
积分:2474
排名:第13064名
原创:108篇
转载:48篇
评论:50条
阅读:2967
阅读:8996
阅读:7540
(1)(4)(1)(5)(8)(4)(5)(3)(2)(3)(3)(6)(3)(1)(5)(7)(4)(1)(20)(16)(7)(12)(2)(7)(12)(5)(2)(1)(2)(3)(1)使用sklearn优雅地进行数据挖掘_python_ThinkSAAS
使用sklearn优雅地进行数据挖掘
使用sklearn优雅地进行数据挖掘
内容来源: 网络
1 使用sklearn进行数据挖掘 1.1 数据挖掘的步骤 1.2 数据初貌 1.3 关键技术2 并行处理 2.1 整体并行处理 2.2 部分并行处理3 流水线处理4 自动化调参5 持久化6 回顾7 总结8 参考资料
1 使用sklearn进行数据挖掘
1.1 数据挖掘的步骤
数据挖掘通常包括数据采集,数据分析,特征工程,训练模型,模型评估等步骤。使用sklearn工具可以方便地进行特征工程和模型训练工作,在中,我们最后留下了一些疑问:特征处理类都有三个方法fit、transform和fit_transform,fit方法居然和模型训练方法fit同名(不光同名,参数列表都一样),这难道都是巧合?
显然,这不是巧合,这正是sklearn的设计风格。我们能够更加优雅地使用sklearn进行特征工程和模型训练工作。此时,不妨从一个基本的数据挖掘场景入手:
我们使用sklearn进行虚线框内的工作(sklearn也可以进行文本特征提取)。通过分析sklearn源码,我们可以看到除训练,预测和评估以外,处理其他工作的类都实现了3个方法:fit、transform和fit_transform。从命名中可以看到,fit_transform方法是先调用fit然后调用transform,我们只需要关注fit方法和transform方法即可。
transform方法主要用来对特征进行转换。从可利用信息的角度来说,转换分为无信息转换和有信息转换。无信息转换是指不利用任何其他信息进行转换,比如指数、对数函数转换等。有信息转换从是否利用目标值向量又可分为无监督转换和有监督转换。无监督转换指只利用特征的统计信息的转换,统计信息包括均值、标准差、边界等等,比如标准化、PCA法降维等。有监督转换指既利用了特征信息又利用了目标值信息的转换,比如通过模型选择特征、LDA法降维等。通过总结常用的转换类,我们得到下表:
参数列表
fit方法有用
sklearn.preprocessing
StandardScaler
无监督
标准化
sklearn.preprocessing
MinMaxScaler
无监督
区间缩放
sklearn.preprocessing
Normalizer
无信息
正则化
sklearn.preprocessing
Binarizer
无信息
定量特征二值化
sklearn.preprocessing
OneHotEncoder
无监督
定性特征编码
sklearn.preprocessing
Imputer
无监督
缺失值计算
sklearn.preprocessing
PolynomialFeatures
无信息
多项式变换(fit方法仅仅生成了多项式的表达式)
sklearn.preprocessing
FunctionTransformer
无信息
自定义函数变换(自定义函数在transform方法中调用)
sklearn.feature_selection
VarianceThreshold
无监督
方差选择法
sklearn.feature_selection
SelectKBest
无监督
自定义特征评分选择法
sklearn.feature_selection
无监督
卡方检验选择法
sklearn.feature_selection
特征+目标值
有监督
递归特征消除法
sklearn.feature_selection
SelectFromModel
特征+目标值
有监督
自定义模型训练选择法
sklearn.decomposition
无监督
PCA降维
sklearn.lda
特征+目标值
有监督
LDA降维
不难看到,只有有信息的转换类的fit方法才实际有用,显然fit方法的主要工作是获取特征信息和目标值信息,在这点上,fit方法和模型训练时的fit方法就能够联系在一起了:都是通过分析特征和目标值,提取有价值的信息,对于转换类来说是某些统计量,对于模型来说可能是特征的权值系数等。另外,只有有监督的转换类的fit和transform方法才需要特征和目标值两个参数。fit方法无用不代表其没实现,而是除合法性校验以外,其并没有对特征和目标值进行任何处理,Normalizer的fit方法实现如下:
1 def fit(self, X, y=None):
"""Do nothing and return the estimator unchanged
This method is just there to implement the usual API and hence
work in pipelines.
X = check_array(X, accept_sparse=&csr&)
return self
基于这些特征处理工作都有共同的方法,那么试想可不可以将他们组合在一起?在本文假设的场景中,我们可以看到这些工作的组合形式有两种:流水线式和并行式。基于流水线组合的工作需要依次进行,前一个工作的输出是后一个工作的输入;基于并行式的工作可以同时进行,其使用同样的输入,所有工作完成后将各自的输出合并之后输出。sklearn提供了包pipeline来完成流水线式和并行式的工作。
1.2 数据初貌
在此,我们仍然使用IRIS数据集来进行说明。为了适应提出的场景,对原数据集需要稍微加工:
1 from numpy import hstack, vstack, array, median, nan
2 from numpy.random import choice
3 from sklearn.datasets import load_iris
5 #特征矩阵加工
6 #使用vstack增加一行含缺失值的样本(nan, nan, nan, nan)
7 #使用hstack增加一列表示花的颜色(0-白、1-黄、2-红),花的颜色是随机的,意味着颜色并不影响花的分类
8 iris.data = hstack((choice([0, 1, 2], size=iris.data.shape[0]+1).reshape(-1,1), vstack((iris.data, array([nan, nan, nan, nan]).reshape(1,-1)))))
9 #目标值向量加工
10 #增加一个目标值,对应含缺失值的样本,值为众数
11 iris.target = hstack((iris.target, array([median(iris.target)])))
1.3 关键技术
并行处理,流水线处理,自动化调参,持久化是使用sklearn优雅地进行数据挖掘的核心。并行处理和流水线处理将多个特征处理工作,甚至包括模型训练工作组合成一个工作(从代码的角度来说,即将多个对象组合成了一个对象)。在组合的前提下,自动化调参技术帮我们省去了人工调参的反锁。训练好的模型是贮存在内存中的数据,持久化能够将这些数据保存在文件系统中,之后使用时无需再进行训练,直接从文件系统中加载即可。
2 并行处理
并行处理使得多个特征处理工作能够并行地进行。根据对特征矩阵的读取方式不同,可分为整体并行处理和部分并行处理。整体并行处理,即并行处理的每个工作的输入都是特征矩阵的整体;部分并行处理,即可定义每个工作需要输入的特征矩阵的列。
2.1 整体并行处理
pipeline包提供了FeatureUnion类来进行整体并行处理:
1 from numpy import log1p
2 from sklearn.preprocessing import FunctionTransformer
3 from sklearn.preprocessing import Binarizer
4 from sklearn.pipeline import FeatureUnion
6 #新建将整体特征矩阵进行对数函数转换的对象
7 step2_1 = (&ToLog&, FunctionTransformer(log1p))
8 #新建将整体特征矩阵进行二值化类的对象
9 step2_2 = (&ToBinary&, Binarizer())
10 #新建整体并行处理对象
11 #该对象也有fit和transform方法,fit和transform方法均是并行地调用需要并行处理的对象的fit和transform方法
12 #参数transformer_list为需要并行处理的对象列表,该列表为二元组列表,第一元为对象的名称,第二元为对象
13 step2 = (&FeatureUnion&, FeatureUnion(transformer_list=[step2_1, step2_2, step2_3]))
2.2 部分并行处理
整体并行处理有其缺陷,在一些场景下,我们只需要对特征矩阵的某些列进行转换,而不是所有列。pipeline并没有提供相应的类,需要我们在FeatureUnion的基础上进行优化:
1 from sklearn.pipeline import FeatureUnion, _fit_one_transformer, _fit_transform_one, _transform_one
2 from sklearn.externals.joblib import Parallel, delayed
3 from scipy import sparse
4 import numpy as np
6 #部分并行处理,继承FeatureUnion
7 class FeatureUnionExt(FeatureUnion):
#相比FeatureUnion,多了idx_list参数,其表示每个并行工作需要读取的特征矩阵的列
def __init__(self, transformer_list, idx_list, n_jobs=1, transformer_weights=None):
self.idx_list = idx_list
FeatureUnion.__init__(self, transformer_list=map(lambda trans:(trans[0], trans[1]), transformer_list), n_jobs=n_jobs, transformer_weights=transformer_weights)
#由于只部分读取特征矩阵,方法fit需要重构
def fit(self, X, y=None):
transformer_idx_list = map(lambda trans, idx:(trans[0], trans[1], idx), self.transformer_list, self.idx_list)
transformers = Parallel(n_jobs=self.n_jobs)(
#从特征矩阵中提取部分输入fit方法
delayed(_fit_one_transformer)(trans, X[:,idx], y)
for name, trans, idx in transformer_idx_list)
self._update_transformer_list(transformers)
return self
#由于只部分读取特征矩阵,方法fit_transform需要重构
def fit_transform(self, X, y=None, **fit_params):
transformer_idx_list = map(lambda trans, idx:(trans[0], trans[1], idx), self.transformer_list, self.idx_list)
result = Parallel(n_jobs=self.n_jobs)(
#从特征矩阵中提取部分输入fit_transform方法
delayed(_fit_transform_one)(trans, name, X[:,idx], y,
self.transformer_weights, **fit_params)
for name, trans, idx in transformer_idx_list)
Xs, transformers = zip(*result)
self._update_transformer_list(transformers)
if any(sparse.issparse(f) for f in Xs):
Xs = sparse.hstack(Xs).tocsr()
else:
Xs = np.hstack(Xs)
return Xs
#由于只部分读取特征矩阵,方法transform需要重构
def transform(self, X):
transformer_idx_list = map(lambda trans, idx:(trans[0], trans[1], idx), self.transformer_list, self.idx_list)
Xs = Parallel(n_jobs=self.n_jobs)(
#从特征矩阵中提取部分输入transform方法
delayed(_transform_one)(trans, name, X[:,idx], self.transformer_weights)
for name, trans, idx in transformer_idx_list)
if any(sparse.issparse(f) for f in Xs):
Xs = sparse.hstack(Xs).tocsr()
else:
Xs = np.hstack(Xs)
return Xs
View Code
在本文提出的场景中,我们对特征矩阵的第1列(花的颜色)进行定性特征编码,对第2、3、4列进行对数函数转换,对第5列进行定量特征二值化处理。使用FeatureUnionExt类进行部分并行处理的代码如下:
1 from numpy import log1p
2 from sklearn.preprocessing import OneHotEncoder
3 from sklearn.preprocessing import FunctionTransformer
4 from sklearn.preprocessing import Binarizer
6 #新建将部分特征矩阵进行定性特征编码的对象
7 step2_1 = (&OneHotEncoder&, OneHotEncoder(sparse=False))
8 #新建将部分特征矩阵进行对数函数转换的对象
9 step2_2 = (&ToLog&, FunctionTransformer(log1p))
10 #新建将部分特征矩阵进行二值化类的对象
11 step2_3 = (&ToBinary&, Binarizer())
12 #新建部分并行处理对象
13 #参数transformer_list为需要并行处理的对象列表,该列表为二元组列表,第一元为对象的名称,第二元为对象
14 #参数idx_list为相应的需要读取的特征矩阵的列
15 step2 = (&FeatureUnionExt&, FeatureUnionExt(transformer_list=[step2_1, step2_2, step2_3], idx_list=[[0], [1, 2, 3], [4]]))
3 流水线处理
pipeline包提供了Pipeline类来进行流水线处理。流水线上除最后一个工作以外,其他都要执行fit_transform方法,且上一个工作输出作为下一个工作的输入。最后一个工作必须实现fit方法,输入为上一个工作的输出;但是不限定一定有transform方法,因为流水线的最后一个工作可能是训练!
根据本文提出的场景,结合并行处理,构建完整的流水线的代码如下:
1 from numpy import log1p
2 from sklearn.preprocessing import Imputer
3 from sklearn.preprocessing import OneHotEncoder
4 from sklearn.preprocessing import FunctionTransformer
5 from sklearn.preprocessing import Binarizer
6 from sklearn.preprocessing import MinMaxScaler
7 from sklearn.feature_selection import SelectKBest
8 from sklearn.feature_selection import chi2
9 from sklearn.decomposition import PCA
10 from sklearn.linear_model import LogisticRegression
11 from sklearn.pipeline import Pipeline
13 #新建计算缺失值的对象
14 step1 = (&Imputer&, Imputer())
15 #新建将部分特征矩阵进行定性特征编码的对象
16 step2_1 = (&OneHotEncoder&, OneHotEncoder(sparse=False))
17 #新建将部分特征矩阵进行对数函数转换的对象
18 step2_2 = (&ToLog&, FunctionTransformer(log1p))
19 #新建将部分特征矩阵进行二值化类的对象
20 step2_3 = (&ToBinary&, Binarizer())
21 #新建部分并行处理对象,返回值为每个并行工作的输出的合并
22 step2 = (&FeatureUnionExt&, FeatureUnionExt(transformer_list=[step2_1, step2_2, step2_3], idx_list=[[0], [1, 2, 3], [4]]))
23 #新建无量纲化对象
24 step3 = (&MinMaxScaler&, MinMaxScaler())
25 #新建卡方校验选择特征的对象
26 step4 = (&SelectKBest&, SelectKBest(chi2, k=3))
27 #新建PCA降维的对象
28 step5 = (&PCA&, PCA(n_components=2))
29 #新建逻辑回归的对象,其为待训练的模型作为流水线的最后一步
30 step6 = (&LogisticRegression&, LogisticRegression(penalty=&l2&))
31 #新建流水线处理对象
32 #参数steps为需要流水线处理的对象列表,该列表为二元组列表,第一元为对象的名称,第二元为对象
33 pipeline = Pipeline(steps=[step1, step2, step3, step4, step5, step6])
4 自动化调参
网格搜索为自动化调参的常见技术之一,grid_search包提供了自动化调参的工具,包括GridSearchCV类。对组合好的对象进行训练以及调参的代码如下:
1 from sklearn.grid_search import GridSearchCV
3 #新建网格搜索对象
4 #第一参数为待训练的模型
#param_grid为待调参数组成的网格,字典格式,键为参数名称(格式“对象名称__子对象名称__参数名称”),值为可取的参数值列表
grid_search = GridSearchCV(pipeline, param_grid={&FeatureUnionExt__ToBinary__threshold&:[1.0, 2.0, 3.0, 4.0], &LogisticRegression__C&:[0.1, 0.2, 0.4, 0.8]})
7 #训练以及调参
8 grid_search.fit(iris.data, iris.target)
5 持久化
externals.joblib包提供了dump和load方法来持久化和加载内存数据:
1 #持久化数据
2 #第一个参数为内存中的对象
3 #第二个参数为保存在文件系统中的名称
4 #第三个参数为压缩级别,0为不压缩,3为合适的压缩级别
5 dump(grid_search, &grid_search.dmp&, compress=3)
6 #从文件系统中加载数据到内存中
7 grid_search = load(&grid_search.dmp&)
6 回顾
类或方法
sklearn.pipeline
Pipeline
流水线处理
sklearn.pipeline
FeatureUnion
并行处理
sklearn.grid_search
GridSearchCV
网格搜索调参
externals.joblib
数据持久化
externals.joblib
从文件系统中加载数据至内存
注意:组合和持久化都会涉及pickle技术,,将lambda定义的函数作为FunctionTransformer的自定义转换函数将不能pickle化。
7 总结
2015年我设计了一个,其以Mysql数据库作为原始数据源,提供了“灵活的”特征提取、特征处理的配置方法,同时重新封装了数据、特征和模型,以方便调度系统识别。说灵活,其实也只是通过配置文件的方式定义每个特征的提取和处理的sql语句。但是纯粹使用sql语句来进行特征处理是很勉强的,除去特征提取以外,我又造了一回轮子,原来sklearn提供了这么优秀的特征处理、工作组合等功能。所以,我在这个博客中先不提任何算法和模型,先从数据挖掘工作的第一步开始,使用基于Python的各个工具把大部分步骤都走了一遍(抱歉,我暂时忽略了特征提取),希望这样的梳理能够少让初学者走弯路吧。
7 参考资料
PHP开发框架
开发工具/编程工具
服务器环境
ThinkSAAS商业授权:
ThinkSAAS为用户提供有偿个性定制开发服务
ThinkSAAS将为商业授权用户提供二次开发指导和技术支持
让ThinkSAAS更好,把建议拿来。
开发客服微信