Orange3机器学习工具的二次开发

4/27/2022 Orange3机器学习数据挖掘

# 1. Orange简介

# 1.1 基本介绍

Orange3 是一款基于Python的数据挖掘和可视化工具,它提供了丰富的数据分析、机器学习和数据挖掘算法,同时也支持可视化分析和交互式数据探索。它提供了友好的界面和丰富的示例工程,使得新手用户也可以快速上手,同时也支持Python脚本,可以满足高级用户的需求。

Orange3

# 1.2 重要概念

Orange中的重要概念包括:

  • 组件(widget)将界面显示、交互控制、逻辑处理等封装为一体的独立功能单元。
  • 类别(category)将具有相似功能的组件组织在一起的逻辑分组。
  • 节点(node)组件的运行时实例,一个组件可以有多个不同的实例。
  • 输入(input)组件可接收处理的不同类型数据。
  • 输出(output)组件对输入数据完成处理后向外发布的不同类型数据。
  • 连接(link)将一个组件的输出连接到另一个组件的输入,类似于Unix的管道机制,输出和输入必须是同种或兼容的数据类型。
  • 工作流(workflow)将组件按照数据的上下游关系串联起来实现特定任务目标的计算模型。
  • 画布(canvas)组件布局和工作流设置的绘图区域。
  • 控制区域(controlArea)用于对组件的运行参数进行配置。
  • 内容区域(mainArea)用于展示该组件对输入数据的处理结果,通常为图表方式。

# 2. 运行项目

实验环境:Macbook Pro 2021,M1 pro芯片,16G内存,macOS Ventura13.3.1系统、Conda 22.11.1

Conda环境的搭建及基本配置就不赘述了,不了解的详见我的另一篇博客:MacOS的基础设置及使用指南 (opens new window)

由于我这里需要二次开发,因此采用了从源码启动的方式,如果只是为了使用,可以直接从 官网 (opens new window) 下载 release 版本。

# 2.1 准备代码与环境

从Github上拉取项目代码,创建并激活Conda虚拟环境。

$ git clone https://github.com/biolab/orange3.git
$ conda create -n conda_orange_env python=3.8
$ conda activate conda_orange_env
1
2
3

安装项目依赖:

$ pip3 install PyQt6 PyQt6-WebEngine
$ pip3 install -r requirements.txt
1
2

其中 requirements-core.txt 里的 openTSNE 依赖安装失败,暂时先把它注释掉,之后单独手动安装。

ERROR: Failed building wheel for openTSNE
Failed to build openTSNE
ERROR: Could not build wheels for openTSNE, which is required to install pyproject.toml-based projects
1
2
3

使用源码自己构建 openTSNE 依赖。

$ git clone https://github.com/pavlin-policar/openTSNE.git
$ cd openTSNE
$ pip3 install .
1
2
3

# 2.2 编译动态链接库

在项目里的Orange目录内,有一些 .pyx、.c、.cpp 的文件,我们需要把它编译成动态链接库(Linux/MacOS是.so,Win是.pyd)才可以使用。

$ find "./Orange" -type f \( -name "*.pyx" -o -name "*.c" -o -name "*.cpp" \) -print

./Orange/data/_valuecount.pyx
./Orange/data/_contingency.pyx
./Orange/data/_variable.pyx
./Orange/data/_io.pyx
./Orange/classification/_tree_scorers.pyx
./Orange/classification/_simple_tree.c
./Orange/distance/_distance.pyx
./Orange/preprocess/_discretize.pyx
./Orange/preprocess/_relieff.pyx
./Orange/projection/_som.pyx
./Orange/widgets/utils/_grid_density.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13

这里我在根目录写了一个 compile.py 脚本统一进行编译。

# -*- coding: utf-8 -*-

import os
import platform
import shutil
import sys
from setuptools import setup, Extension
from Cython.Build import cythonize
import numpy as np

# 在运行前模拟命令行参数
sys.argv = [sys.argv[0], 'build_ext', '--inplace']

# 定义扩展
extensions = [
    Extension("_valuecount", ["./Orange/data/_valuecount.pyx"], include_dirs=[np.get_include()]),
    Extension("_contingency", ["./Orange/data/_contingency.pyx"], include_dirs=[np.get_include()]),
    Extension("_variable", ["./Orange/data/_variable.pyx"], include_dirs=[np.get_include()]),
    Extension("_io", ["./Orange/data/_io.pyx"], include_dirs=[np.get_include()]),
    Extension("_tree_scorers", ["./Orange/classification/_tree_scorers.pyx"], include_dirs=[np.get_include()]),
    Extension("_simple_tree", ["./Orange/classification/_simple_tree.c"], include_dirs=[np.get_include()]),
    Extension("_distance", ["./Orange/distance/_distance.pyx"], include_dirs=[np.get_include()]),
    Extension("_discretize", ["./Orange/preprocess/_discretize.pyx"], include_dirs=[np.get_include()]),
    Extension("_relieff", ["./Orange/preprocess/_relieff.pyx"], include_dirs=[np.get_include()]),
    Extension("_som", ["./Orange/projection/_som.pyx"], include_dirs=[np.get_include()]),
    Extension("_grid_density", ["./Orange/widgets/utils/_grid_density.cpp"], include_dirs=[np.get_include()])
]

# 运行setup
setup(
    ext_modules=cythonize(extensions)
)

# 构建目录和扩展映射
build_base = './build'
lib_directories = [d for d in os.listdir(build_base) if d.startswith("lib") and os.path.isdir(os.path.join(build_base, d))]
build_directory = build_base + "/" + lib_directories[0]

# 确定操作系统类型
ext = ".so" if platform.system() != "Windows" else ".pyd"

# 获取所有编译的文件
compile_files = [f for f in os.listdir(build_directory) if f.endswith(ext)]

# 定义目标目录映射
target_dict = {
    "_valuecount": "./Orange/data/",
    "_contingency": "./Orange/data/",
    "_variable": "./Orange/data/",
    "_io": "./Orange/data/",
    "_tree_scorers": "./Orange/classification/",
    "_simple_tree": "./Orange/classification/",
    "_distance": "./Orange/distance/",
    "_discretize": "./Orange/preprocess/",
    "_relieff": "./Orange/preprocess/",
    "_som": "./Orange/projection/",
    "_grid_density": "./Orange/widgets/utils/"
}

# 移动动态链接库文件
for compile_file in compile_files:
    # 去掉文件扩展名,并切分
    split_name = compile_file.split('.')[0].split('_')
    # 重建基础名称,考虑可能的多个下划线部分
    base_name = '_' + '_'.join(split_name[1:])
    # 根据字典获取目标目录
    target_directory = target_dict.get(base_name)
    if target_directory:
        source_path = os.path.join(build_directory, compile_file)
        target_path = os.path.join(target_directory, compile_file)
        shutil.move(source_path, target_path)
    else:
        print(f"未找到目标目录: {base_name}")

# 递归删除build目录
shutil.rmtree("./build")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76

安装Cython和numpy依赖,然后将其编译成动态链接库。

$ pip3 install Cython numpy
$ python3 compile.py
1
2

注:如果没有编译动态链接库,或者与系统版本不匹配,启动时会有类似于如下的报错。

from Orange.data import _variable
ImportError: cannot import name '_variable' from partially initialized module 'Orange.data' (most likely due to a circular import) 
1
2

# 2.3 启动Orange3项目

首次启动项目建议输出调试信息,以排查解决组件报错。

$ python3 -m Orange.canvas  // 启动项目
$ python3 -m Orange.canvas -l 4 --no-splash  // 启动项目,跳过启动屏幕窗口,并输出更多调试信息
1
2

若想要使用Python文件进行启动,可以在根目录新建一个main.py。

# -*- coding: utf-8 -*-

from Orange.canvas.__main__ import main

if __name__ == "__main__":
    main()
1
2
3
4
5
6

注意事项:

[1] 如果启动时,控制台报错 ModuleNotFoundError: No module named 'Orange.version',执行setup.py文件,即可在./Orange目录下创建出 version.py。

# THIS FILE IS GENERATED FROM ORANGE SETUP.PY
short_version = '3.36.0'
version = '3.36.0'
full_version = '3.36.0.dev0+'
git_revision = ''
release = False

if not release:
    version = full_version
    short_version += ".dev"
1
2
3
4
5
6
7
8
9
10

[2] 如果启动后,左侧工具栏不显示,并且控制台出现如下报错,执行 pip3 install --upgrade scikit-learn 命令即可。

ImportError: cannot import name 'METRIC_MAPPING64' from 'sklearn.metrics._dist_metrics' (/Users/xxx/miniforge3/envs/conda_orange_env/lib/python3.8/site-packages/sklearn/metrics/_dist_metrics.cpython-38-darwin.so)
1

首次启动较慢,耐心等待一会儿,启动成功后的界面如下:

启动Orange项目

# 3. 组件概述及使用

# 3.1 组件功能概述

从 Orange 主界面图上可见,Orange 左边栏默认提供了 6 个组件集,组件的图标也很直观地展示了组件的功能。

  • 数据( Data ):常见格式数据的导入、数据库数据读取、数据保存、抽样、创建透视表、转换、设置相关系数等。
  • 转换( Transform ):主要用于数据预处理和特征工程,包括数据清洗、标准化、特征选择等功能。
  • 可视化( Visualize ):树状图、箱体图、散点图、直方图、热图等。
  • 模型( Model ):各类机器学习模型,如 KNN、随机森林、SVM、逻辑回归、神经网络、贝叶斯,模型加载和保存。
  • 评估( Evaluate ):交叉验证、抽样程序、ROC 曲线等。
  • 无监督算法( Unsupervised ):各类数据降维算法,如 PCA、t-SNE;各类无监督算法模型,如 K-Means 分析、层次聚类分析等。

在菜单栏的Options——Add-ons...处,可以添加更多的组件。

Orange安装官方组件

# 3.2 官方使用示例

项目启动时,在欢迎页处有Examples,点开之后有官方提供的使用示例,加载后直接就能用。

Orange3官方示例

更多的应用实例,可见官网的 Screenshots 页面。

Orange官网应用实例截图

# 4. 自定义组件开发

除了使用Orange自带的数据导入、预处理、建模、评估、可视化等一系列基础组件之外,用户还可以自己开发组件并集成进Orange平台。

# 4.1 以插件形式动态集成

Orange采用了PyQt的技术架构,自定义组件详见 开发文档 (opens new window),示例工程详见 orange3-example-addon (opens new window),其项目结构如下所示:

.
├── MANIFEST.in
├── README.md
├── README.pypi
├── doc
│   ├── Makefile
│   ├── conf.py
│   ├── index.rst
│   ├── make.bat
│   └── widgets
│       ├── icons
│       │   └── mywidget.png
│       └── mywidget.md
├── orangecontrib
│   ├── __init__.py
│   └── example
│       ├── __init__.py
│       ├── tests
│       │   ├── __init__.py
│       │   └── test_example.py
│       ├── tutorials
│       │   ├── __init__.py
│       │   └── example_tutorial.ows
│       └── widgets
│           ├── __init__.py
│           ├── icons
│           │   ├── category.svg
│           │   └── mywidget.svg
│           └── mywidget.py
├── screenshot.png
└── setup.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

orangecontrib 是 Orange 官方建议的包的顶级名称,可采用也可不采用,example 是用户创建的包的名称,包含两个目录,widgets 中放置组件的源代码文件,tutorials 中放置该组件示例教程的 ows 文件。

自定义组件并不强制放在Orange项目工程内部,可以做成独立的安装包发布并安装。通过Python提供的插件机制,Orange启动时自动发现自定义组件并装载,其原理可参见 该文档 (opens new window)。在setup.py中对应的插件部分的配置代码如下所示,在ENTRY_POINTS中设置了3个插件注入点,最重要的就是orange.widgets用于装载自定义组件。

ENTRY_POINTS = {
    # Entry points that marks this package as an orange add-on. If set, addon will
    # be shown in the add-ons manager even if not published on PyPi.
    'orange3.addon': (
        'example = orangecontrib.example',
    ),
    # Entry point used to specify packages containing tutorials accessible
    # from welcome screen. Tutorials are saved Orange Workflows (.ows files).
    'orange.widgets.tutorials': (
        # Syntax: any_text = path.to.package.containing.tutorials
        'exampletutorials = orangecontrib.example.tutorials',
    ),

    # Entry point used to specify packages containing widgets.
    'orange.widgets': (
        # Syntax: category name = path.to.package.containing.widgets
        # Widget category specification can be seen in
        #    orangecontrib/example/widgets/__init__.py
        'Examples = orangecontrib.example.widgets',
    ),

    # Register widget help
    "orange.canvas.help": (
        'html-index = orangecontrib.example.widgets:WIDGET_HELP_PATH',)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

组件源文件mywidget.py的代码如下所示:

from Orange.data import Table
from Orange.widgets import gui
from Orange.widgets.settings import Setting
from Orange.widgets.widget import OWWidget, Input, Output, Msg


class MyWidget(OWWidget):
    # Widget needs a name, or it is considered an abstract widget
    # and not shown in the menu.
    name = "Hello World"
    description = "Tell me more about yourself."
    icon = "icons/mywidget.svg"
    priority = 100  # where in the widget order it will appear
    keywords = ["widget", "data"]
    want_main_area = False
    resizing_enabled = False
    
    label = Setting("")
    
    class Inputs:
        # specify the name of the input and the type
        data = Input("Data", Table)

    class Outputs:
        # if there are two or more outputs, default=True marks the default output
        data = Output("Data", Table, default=True)
    
    # same class can be initiated for Error and Information messages
    class Warning(OWWidget.Warning):
        warning = Msg("My warning!")

    def __init__(self):
        super().__init__()
        self.data = None
        
        self.label_box = gui.lineEdit(
            self.controlArea, self, "label", box="Text", callback=self.commit)
        
    @Inputs.data
    def set_data(self, data):
        if data:
            self.data = data
        else:
            self.data = None
            
    def commit(self):
        self.Outputs.data.send(self.data)
    
    def send_report(self):
        # self.report_plot() includes visualizations in the report
        self.report_caption(self.label)


if __name__ == "__main__":
    from Orange.widgets.utils.widgetpreview import WidgetPreview  # since Orange 3.20.0
    WidgetPreview(MyWidget).run()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

注:mywidget.py 可以脱离平台单独运行及调试,也可以添加进 Orange 平台里运行。

把之前项目编译了动态链接库的Orange目录拷贝到项目根目录,使用如下命令将组件添加进去。

$ pip3 install -e .
1

之后重新启动项目,即可看到自定义组件被添加进去了。

$ python3 -m Orange.canvas -l 4 --no-splash  
1

Orange自定义组件开发官方示例

注:如果要卸载组件,也是在菜单栏的Options——Add-ons...处取消勾选进行卸载。

# 4.2 在平台里固定集成

Step1:在./Orange/widgets目录新建一个 example 文件夹,将 orange3-example-addon (opens new window) 示例工程的 widgets 部分整个拷贝进来,修改__init__.py如下:

NAME = "example"

ID = "orange.widgets.example"

DESCRIPTION = """示例工具模块。"""

ICON = "icons/category.svg"

BACKGROUND = "#FFD39F"

PRIORITY = 0
1
2
3
4
5
6
7
8
9
10
11

Step2:再在 ./Orange/widgets/__init__.py文件里引入 orange.widgets.example 即可。

import os
import sysconfig

import pkg_resources

from orangecanvas.registry import CategoryDescription
from orangecanvas.registry.utils import category_from_package_globals
import orangewidget.workflow.discovery


# Entry point for main Orange categories/widgets discovery
def widget_discovery(discovery):
    # type: (orangewidget.workflow.discovery.WidgetDiscovery) -> None
    dist = pkg_resources.get_distribution("Orange3")
    pkgs = [
        "Orange.widgets.data",
        "Orange.widgets.example",    # 添加这行代码
        "Orange.widgets.visualize",
        "Orange.widgets.model",
        "Orange.widgets.evaluate",
        "Orange.widgets.unsupervised",
    ]
    for pkg in pkgs:
        discovery.handle_category(category_from_package_globals(pkg))
    # manually described category (without 'package' definition)
    discovery.handle_category(
        CategoryDescription(
            name="Transform",
            priority=1,
            background="#FF9D5E",
            icon="data/icons/Transform.svg",
            package=__package__,
        )
    )
    discovery.handle_category(
        CategoryDescription(
            name="Orange Obsolete",
            package=__package__,
            hidden=True,
        )
    )
    for pkg in pkgs:
        discovery.process_category_package(pkg, distribution=dist)
    discovery.process_widget_module("Orange.widgets.obsolete.owtable")


WIDGET_HELP_PATH = (
    ("{DEVELOP_ROOT}/doc/visual-programming/build/htmlhelp/index.html", None),
    (os.path.join(sysconfig.get_path("data"),
                  "share/help/en/orange3/htmlhelp/index.html"),
     None),
    ("https://docs.biolab.si/orange/3/visual-programming/", ""),
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

之后重新启动项目,即可看到自定义组件被添加进去了。

$ python3 -m Orange.canvas -l 4 --no-splash  
1

Orange固定集成自定义组件

# 5. 橙现智能Orange3汉化版

# 5.1 汉化版项目简介

橙现智能基于Orange3汉化及二次开发,仍然免费开源,是一个简单易用的可视化人工智能应用工具。

橙现智能Orange3汉化版

# 5.2 启动Orange3汉化版

拉取项目代码,依旧使用之前的 conda_orange_env 环境

$ git clone https://github.com/szzyiit/orange3.git
1

按理说下载下来之后,编译完动态链接库,再使用 pip3 install orange3-zh命令安装汉化依赖就行了。

但我这里安不上这个依赖,就把它下载、解压、重命名为 Orange3-zh 放到项目根目录里了。

$ wget http://mirrors.aliyun.com/pypi/packages/17/46/c8dd43aad6835b5cb458bc2e099ef8ef182cf2c1d0997f99aa23a38a330e/Orange3-zh-3.33.1.tar.gz 
1

修改 ./Orange/widgets/__init__.py 文件里对 Orange3-zh 的引用。

# Entry point for main Orange categories/widgets discovery
def widget_discovery(discovery):
    # type: (orangewidget.workflow.discovery.WidgetDiscovery) -> None
    # dist = pkg_resources.get_distribution("Orange3-zh")
    dist = pkg_resources.working_set.add_entry('Orange3-zh')  # 修改成这样
    pkgs = [
        "Orange.widgets.data",
        "Orange.widgets.visualize",
        "Orange.widgets.model",
        "Orange.widgets.evaluate",
        "Orange.widgets.unsupervised",
        "Orange.widgets.reinforcement",
        "Orange.widgets.deepLearning",
    ]
    for pkg in pkgs:
        discovery.handle_category(category_from_package_globals(pkg))
    # manually described category (without 'package' definition)
    # discovery.handle_category(
    #     CategoryDescription(
    #         name="变换(Transform)",
    #         priority=1,
    #         background="#FF9D5E",
    #         icon="data/icons/Transform.svg",
    #         package=__package__,
    #     )
    # )
    for pkg in pkgs:
        discovery.process_category_package(pkg, distribution=dist)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

按照之前的流程进行启动,把缺失的依赖补全即可。

# 6. 参考资料

[1] 易上手的数据挖掘、可视化与机器学习工具:Orange介绍 from 夜行人 (opens new window)

[2] Orange3-二:使用Orange3对图片进行数据挖掘 from 知乎 (opens new window)

[3] Orange-自定义组件开发(一) from 知乎 (opens new window)

[4] 使用Orange3做差异化表达分析 from 知乎 (opens new window)

[5] Orange3的Distributions、Rank和Sieve Diagram from 简书 (opens new window)

[6] Python | Orange:不用写代码,也能做数字化审计(一) from 墨天轮 (opens new window)

[7] Orange3-三: 用Orange3处理文本数据 from 知乎 (opens new window)

[8] Orange数据挖掘工具介绍 from 台部落 (opens new window)

[9] 使用orange进行聚类分析 from CSDN (opens new window)

[10] pyinstaller打包Orange3 from CSDN (opens new window)

[11] 用于构建Orange应用程序安装程序的脚本 from Github (opens new window)

[12] Orange:一个基于 Python 的数据挖掘和机器学习平台 from CSDN (opens new window)

Last Updated: 4/3/2024, 10:01:17 AM