Skip to content

Commit c440214

Browse files
first commit
1 parent 19623d3 commit c440214

File tree

10 files changed

+55
-73
lines changed

10 files changed

+55
-73
lines changed

README.md

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.13968099.svg)](https://doi.org/10.5281/zenodo.13968099)
44

55

6-
The jUCS implementation includes UCS <sup><a id="ref1"></a>[[1]](#1)</sup> and Fuzzy-UCS <sup><a id="ref2"></a>[[2]](#2)</sup> codified in Julia, as described in the *ACM Transactions on Evolutionary Learning and Optimization* (TELO) article:
6+
The jUCS implementation includes UCS <sup><a id="ref1"></a>[[1]](#1)</sup> and Fuzzy-UCS <sup><a id="ref2"></a>[[2]](#2)</sup> codified in Julia, as described in the ACM Transactions on Evolutionary Learning and Optimization (TELO) article:
77

8-
>Hiroki Shiraishi, Hisao Ishibuchi, and Masaya Nakata. "**A Class Inference Scheme With Dempster-Shafer Theory for Learning Fuzzy-Classifier Systems**." ACM Transactions on Evolutionary Learning and Optimization (2025).
8+
>Hiroki Shiraishi, Hisao Ishibuchi, and Masaya Nakata. 2025. **A Class Inference Scheme With Dempster-Shafer Theory for Learning Fuzzy-Classifier Systems**. ACM Trans. Evol. Learn. Optim. Just Accepted (February 2025). https://doi.org/10.1145/3717613
99
1010
In this article, we propose a novel class inference scheme for Learning Fuzzy-Classifier Systems (a.k.a. evolutionary fuzzy rule-based classification systems) based on the Dempster-Shafer Theory of Evidence. Our scheme enhances the handling of uncertainty in classification by calculating belief masses for each class and an *"I don't know"* state, then combining them to infer a class. When applied to the Fuzzy-UCS classifier system, our scheme improves classification performance on 30 real-world datasets compared to conventional voting- and single-winner-based class inference schemes.
1111

@@ -399,18 +399,19 @@ These codes will run experiments on the 30 datasets used in the main article and
399399

400400
The copyright of jUCS belongs to the authors in the [Evolutionary Intelligence Research Group](http://www.nkt.ynu.ac.jp/en/) (Nakata Lab) at Yokohama National University, Japan. You are free to use this code for research purposes. In such cases, we kindly request that you cite the following article:
401401

402-
>Hiroki Shiraishi, Hisao Ishibuchi, and Masaya Nakata. "**A Class Inference Scheme With Dempster-Shafer Theory for Learning Fuzzy-Classifier Systems**." ACM Transactions on Evolutionary Learning and Optimization (2025).
402+
>Hiroki Shiraishi, Hisao Ishibuchi, and Masaya Nakata. 2025. **A Class Inference Scheme With Dempster-Shafer Theory for Learning Fuzzy-Classifier Systems**. ACM Trans. Evol. Learn. Optim. Just Accepted (February 2025). https://doi.org/10.1145/3717613
403403
404404
```bibtex
405405
@article{shiraishi2025class,
406-
title={A Class Inference Scheme With Dempster-Shafer Theory for Learning Fuzzy-Classifier Systems},
407-
author={Shiraishi, Hiroki and Ishibuchi, Hisao and Nakata, Masaya},
408-
journal={ACM Transactions on Evolutionary Learning and Optimization},
409-
volume={},
410-
number={},
411-
pages={},
412-
year={2025},
413-
publisher={ACM New York, NY}
406+
author = {Shiraishi, Hiroki and Ishibuchi, Hisao and Nakata, Masaya},
407+
title = {A Class Inference Scheme With Dempster-Shafer Theory for Learning Fuzzy-Classifier Systems},
408+
year = {2025},
409+
publisher = {Association for Computing Machinery},
410+
address = {New York, NY, USA},
411+
doi = {10.1145/3717613},
412+
note = {Just Accepted},
413+
journal = {ACM Trans. Evol. Learn. Optim.},
414+
month = feb
414415
}
415416
```
416417

@@ -426,13 +427,13 @@ The copyright of jUCS belongs to the authors in the [Evolutionary Intelligence R
426427
[3] Christopher Stone and Larry Bull. "**For real! XCS with continuous-valued inputs**." Evolutionary Computation 11.3 (2003): 299-336. https://doi.org/10.1162/106365603322365315 [[]](#ref3)
427428

428429
<a id="4"></a>
429-
[4] Andras Bardossy, and Lucien Duckstein. **Fuzzy rule-based modeling with applications to geophysical, biological, and engineering systems**. CRC press, 1995. https://doi.org/10.1201/9780138755133 [[]](#ref4)
430+
[4] Andras Bardossy and Lucien Duckstein. **Fuzzy rule-based modeling with applications to geophysical, biological, and engineering systems**. CRC press, 1995. https://doi.org/10.1201/9780138755133 [[]](#ref4)
430431

431432
<a id="5"></a>
432433
[5] Hisao Ishibuchi, Tomoharu Nakashima, and Tadahiko Murata. "**Performance evaluation of fuzzy classifier systems for multidimensional pattern classification problems**." IEEE Transactions on Systems, Man, and Cybernetics, Part B (Cybernetics) 29.5 (1999): 601-618. https://doi.org/10.1109/3477.790443 [[]](#ref5)
433434

434435
<a id="6"></a>
435-
[6] Ryan J. Urbanowicz, and Will N. Browne. **Introduction to learning classifier systems**. Springer, 2017. https://books.google.co.jp/books?id=C6QxDwAAQBAJ [[]](#ref6)
436+
[6] Ryan J. Urbanowicz and Will N. Browne. **Introduction to learning classifier systems**. Springer, 2017. https://books.google.co.jp/books?id=C6QxDwAAQBAJ [[]](#ref6)
436437

437438
<a id="7"></a>
438439
[7] Manuel Valenzuela-Rendón. "**The fuzzy classifier system: Motivations and first results**." Parallel Problem Solving from Nature: 1st Workshop, PPSN I Dortmund, FRG, October 1–3, 1990 Proceedings 1. Springer Berlin Heidelberg, 1991. https://doi.org/10.1007/BFb0029774 [[]](#ref7)

environment/real_world.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ function shuffle_train_and_test_data!(self::Environment)
110110

111111
end
112112

113-
function state(self::Environment)::Vector{Union{Float64, String}}
113+
function state(self::Environment)::Vector{Union{Float64, Int64, String}}
114114
if self.is_exploit == false
115115
# Use train data
116116
shuffle_index_array_and_reset_row_index!(self)
@@ -121,7 +121,7 @@ function state(self::Environment)::Vector{Union{Float64, String}}
121121
end
122122
end
123123

124-
function answer(self::Environment, state::Vector{Union{Float64, String}})::Int64
124+
function answer(self::Environment, state::Vector{Union{Float64, Int64, String}})::Int64
125125
if self.is_exploit == false
126126
# Use train data
127127
return Int64(self.train_data[self.index_array[self.row_index], size(self.train_data, 2)])

fuzzy-ucs/fcondition.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ function CNF(vs, s, m, l, vl)
1313
end
1414

1515
"Calculate the membership value for a given input"
16-
function get_membership_value(self::CNF, x::Union{Float64, String})::Float64
16+
function get_membership_value(self::CNF, x::Union{Float64, Int64, String})::Float64
1717
vs, s, m, l, vl = self.vs, self.s, self.m, self.l, self.vl
1818

1919
# If input is "?", return full membership

fuzzy-ucs/fhelper.jl

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -186,9 +186,9 @@ function output_full_score_for_csv(current_epoch::Int64, num_epoch::Int64, env::
186186
end
187187

188188
"Get Accuracy, Precision, Recall, and F1"
189-
function get_scores_per_epoch(seed::Int64, fucs::FUCS, data::Array{Union{Float64, Int64, String}, 2}, changed_data::Array{Union{Float64, String}, 2}, inference::String)::Tuple{Float64, Float64, Float64, Float64}
189+
function get_scores_per_epoch(seed::Int64, fucs::FUCS, data::Array{Union{Float64, Int64, String}, 2}, inference::String)::Tuple{Float64, Float64, Float64, Float64}
190190
true_labels::Vector{Int64} = [Int64(data[row_index, size(data, 2)]) for row_index in 1:size(data, 1)]
191-
predicted_labels::Vector{Int64} = [class_inference(seed, fucs, changed_data[row_index, 1:size(changed_data, 2)], inference) for row_index in 1:size(changed_data, 1)]
191+
predicted_labels::Vector{Int64} = [class_inference(seed, fucs, data[row_index, 1:(size(data, 2)-1)], inference) for row_index in 1:size(data, 1)]
192192

193193
# Determine the common levels
194194
all_levels = unique([true_labels; predicted_labels])
@@ -197,7 +197,7 @@ function get_scores_per_epoch(seed::Int64, fucs::FUCS, data::Array{Union{Float64
197197
cat_predicted_labels = categorical(predicted_labels, levels=all_levels, ordered=true)
198198

199199
# Step 1: Calculate confusion matrix
200-
cm = confusion_matrix(cat_true_labels, cat_predicted_labels)
200+
cm = confusion_matrix(cat_predicted_labels, cat_true_labels)
201201

202202
# Step 2: Calculate precision, recall, and F1 for each class
203203
precision_per_class = Dict{Int64, Float64}()
@@ -223,21 +223,21 @@ function get_scores_per_epoch(seed::Int64, fucs::FUCS, data::Array{Union{Float64
223223
f1_per_class[c] = isnan(f1) ? 0 : f1
224224
end
225225

226-
# Step 3: Calculate Macro Accuracy, Precision, Recall, and F1
227-
macro_accuracy = accuracy(true_labels, predicted_labels) * 100
226+
# Step 3: Calculate Accuracy, Precision, Recall, and F1
227+
overall_accuracy = accuracy(true_labels, predicted_labels) * 100
228228
macro_precision = mean(values(precision_per_class)) * 100
229229
macro_recall = mean(values(recall_per_class)) * 100
230230
macro_f1 = mean(values(f1_per_class)) * 100
231231

232-
return macro_accuracy, macro_precision, macro_recall, macro_f1
232+
return overall_accuracy, macro_precision, macro_recall, macro_f1
233233
end
234234

235-
function get_idk_per_epoch(fucs::FUCS, data::Array{Union{Float64, Int64, String}, 2}, changed_data::Array{Union{Float64, String}, 2})::Float64
236-
idk_array::Vector{Float64} = [calculate_idk(fucs, changed_data[row_index, 1:size(changed_data, 2)]) for row_index in 1:size(changed_data, 1)]
235+
function get_idk_per_epoch(fucs::FUCS, data::Array{Union{Float64, Int64, String}, 2})::Float64
236+
idk_array::Vector{Float64} = [calculate_idk(fucs, data[row_index, 1:(size(data, 2)-1)]) for row_index in 1:size(data, 1)]
237237
return mean(idk_array)
238238
end
239239

240-
function calculate_idk(fucs::FUCS, state::Vector{Union{Float64, String}})::Float64
240+
function calculate_idk(fucs::FUCS, state::Vector{Union{Float64, Int64, String}})::Float64
241241
match_set::Vector{FClassifier} = @views generate_match_set(fucs, state, -1, true)
242242
css = @views generate_conjunctive_sum_set(fucs, match_set)
243243
if css[end-1] != 1
@@ -305,9 +305,9 @@ function conjunctive_masses(mass_all::Vector{Float64}, mass_clas::Vector{Float64
305305
end
306306

307307
# {Float64, Int64, String} -> {Float64, String}
308-
function change_array_type(original_array::Array{Union{Float64, Int64, String}, 2})::Array{Union{Float64, String}, 2}
308+
function change_array_type(original_array::Array{Union{Float64, Int64, String}, 2})::Array{Union{Float64, Int64, String}, 2}
309309
original_array = original_array[:, 1:end-1]
310-
converted_array = Array{Union{Float64, String}, 2}(undef, size(original_array))
310+
converted_array = Array{Union{Float64, Int64, String}, 2}(undef, size(original_array))
311311

312312
for i in 1:size(original_array, 1)
313313
for j in 1:size(original_array, 2)
@@ -324,7 +324,7 @@ function change_array_type(original_array::Array{Union{Float64, Int64, String},
324324
return converted_array
325325
end
326326

327-
function class_inference(seed::Int64, fucs::FUCS, state::Vector{Union{Float64, String}}, inference::String)
327+
function class_inference(seed::Int64, fucs::FUCS, state::Vector{Union{Float64, Int64, String}}, inference::String)
328328
match_set::Vector{FClassifier} = @views generate_match_set(fucs, state, -1, true)
329329

330330
if inference == "vote"

fuzzy-ucs/fucs.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ end
1919
"Main experiment loop for Fuzzy-UCS"
2020
function run_experiment(self::FUCS)
2121
# Get current state and correct answer from environment
22-
curr_state::Vector{Union{Float64, String}} = state(self.env)
22+
curr_state::Vector{Union{Float64, Int64, String}} = state(self.env)
2323
curr_answer::Int64 = answer(self.env, curr_state)
2424

2525
# Generate match set based on current state
@@ -126,7 +126,7 @@ function set_mass(experienced_match_set::Vector{FClassifier})
126126
end
127127

128128
"Generate the match set for a given state and answer"
129-
function generate_match_set(self::FUCS, state::Vector{Union{Float64, String}}, answer::Int64, do_exploit=false)::Vector{FClassifier}
129+
function generate_match_set(self::FUCS, state::Vector{Union{Float64, Int64, String}}, answer::Int64, do_exploit=false)::Vector{FClassifier}
130130
# Calculate matching degrees for all classifiers
131131
set_matching_degree(self, state)
132132

@@ -152,14 +152,14 @@ function generate_match_set(self::FUCS, state::Vector{Union{Float64, String}}, a
152152
end
153153

154154
"Set the matching degree for all classifiers in the population"
155-
function set_matching_degree(self::FUCS, state::Vector{Union{Float64, String}})
155+
function set_matching_degree(self::FUCS, state::Vector{Union{Float64, Int64, String}})
156156
@simd for clas in self.population
157157
clas.matching_degree = get_matching_degree(clas, state)
158158
end
159159
end
160160

161161
"Calculate the matching degree of a classifier for a given state"
162-
function get_matching_degree(clas::FClassifier, state::Vector{Union{Float64, String}})::Float64
162+
function get_matching_degree(clas::FClassifier, state::Vector{Union{Float64, Int64, String}})::Float64
163163
matching_degree::Float64 = 1.0
164164
@simd for i in 1:length(state)
165165
# Multiply membership values for each attribute
@@ -192,7 +192,7 @@ function do_covering(self::FUCS, match_set::Vector{FClassifier}, answer::Int64):
192192
end
193193

194194
"Generate a covering classifier for the current state and answer"
195-
function generate_covering_classifier(self::FUCS, state::Vector{Union{Float64, String}}, answer::Int64)::FClassifier
195+
function generate_covering_classifier(self::FUCS, state::Vector{Union{Float64, Int64, String}}, answer::Int64)::FClassifier
196196
# Create a new fuzzy classifier
197197
clas::FClassifier = FClassifier(self.parameters, self.env, state)
198198
# Set the weight for the correct action to 1

fuzzy-ucs/main.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ using DataFrames
55
using Dates
66

77
function parse_commandline()
8-
s = ArgParseSettings(description="Fuzzy-UCS Classifier System")
8+
s = ArgParseSettings(description="The Fuzzy-UCS classifier system")
99
@add_arg_table s begin
1010
"--num_trials"
1111
help = "The number of trials"
@@ -156,11 +156,11 @@ function main_csv(args; now_str=Dates.format(Dates.now(), "Y-m-d-H-M-S"))
156156

157157
# Test Phase
158158
env.is_exploit = true
159-
train_accuracy::Float64, train_precision::Float64, train_recall::Float64, train_f1::Float64 = get_scores_per_epoch(env.seed, fucs, env.train_data, change_array_type(env.train_data), args["inference"])
160-
test_accuracy::Float64, test_precision::Float64, test_recall::Float64, test_f1::Float64 = get_scores_per_epoch(env.seed, fucs, env.test_data, change_array_type(env.test_data), args["inference"])
159+
train_accuracy::Float64, train_precision::Float64, train_recall::Float64, train_f1::Float64 = get_scores_per_epoch(env.seed, fucs, env.train_data, args["inference"])
160+
test_accuracy::Float64, test_precision::Float64, test_recall::Float64, test_f1::Float64 = get_scores_per_epoch(env.seed, fucs, env.test_data, args["inference"])
161161
if args["inference"] == "ds" && args["idk"]
162-
train_idk::Float64 = get_idk_per_epoch(fucs, env.train_data, change_array_type(env.train_data))
163-
test_idk::Float64 = get_idk_per_epoch(fucs, env.test_data, change_array_type(env.test_data))
162+
train_idk::Float64 = get_idk_per_epoch(fucs, env.train_data)
163+
test_idk::Float64 = get_idk_per_epoch(fucs, env.test_data)
164164
train_idk_list[e] = train_idk
165165
test_idk_list[e] = test_idk
166166
end

ucs/classifier.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ function Classifier(parameters, env, state)
4141
end
4242

4343
"Check if a classifier's condition matches a given state"
44-
function does_match(condition::Vector{UBR}, state::Vector{Union{Float64, String}})::Bool
44+
function does_match(condition::Vector{UBR}, state::Vector{Union{Float64, Int64, String}})::Bool
4545
for i in 1:length(state)
4646
if state[i] == "?"
4747
continue

ucs/helper.jl

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,9 @@ end
173173

174174

175175
"Get Accuracy, Precision, Recall, and F1"
176-
function get_scores_per_epoch(seed::Int64, ucs::UCS, data::Array{Union{Float64, Int64, String}, 2}, changed_data::Array{Union{Float64, String}, 2})::Tuple{Float64, Float64, Float64, Float64}
176+
function get_scores_per_epoch(seed::Int64, ucs::UCS, data::Array{Union{Float64, Int64, String}, 2})::Tuple{Float64, Float64, Float64, Float64}
177177
true_labels::Vector{Int64} = [Int64(data[row_index, size(data, 2)]) for row_index in 1:size(data, 1)]
178-
predicted_labels::Vector{Int64} = [class_inference(seed, ucs, changed_data[row_index, 1:size(changed_data, 2)]) for row_index in 1:size(changed_data, 1)]
178+
predicted_labels::Vector{Int64} = [class_inference(seed, ucs, data[row_index, 1:(size(data, 2)-1)]) for row_index in 1:size(data, 1)]
179179

180180
# Determine the common levels
181181
all_levels = unique([true_labels; predicted_labels])
@@ -184,7 +184,7 @@ function get_scores_per_epoch(seed::Int64, ucs::UCS, data::Array{Union{Float64,
184184
cat_predicted_labels = categorical(predicted_labels, levels=all_levels, ordered=true)
185185

186186
# Step 1: Calculate confusion matrix
187-
cm = confusion_matrix(cat_true_labels, cat_predicted_labels)
187+
cm = confusion_matrix(cat_predicted_labels, cat_true_labels)
188188

189189
# Step 2: Calculate precision, recall, and F1 for each class
190190
precision_per_class = Dict{Int64, Float64}()
@@ -210,35 +210,16 @@ function get_scores_per_epoch(seed::Int64, ucs::UCS, data::Array{Union{Float64,
210210
f1_per_class[c] = isnan(f1) ? 0 : f1
211211
end
212212

213-
# Step 3: Calculate Macro Accuracy, Precision, Recall, and F1
214-
macro_accuracy = accuracy(true_labels, predicted_labels) * 100
213+
# Step 3: Calculate Accuracy, Precision, Recall, and F1
214+
overall_accuracy = accuracy(true_labels, predicted_labels) * 100
215215
macro_precision = mean(values(precision_per_class)) * 100
216216
macro_recall = mean(values(recall_per_class)) * 100
217217
macro_f1 = mean(values(f1_per_class)) * 100
218218

219-
return macro_accuracy, macro_precision, macro_recall, macro_f1
219+
return overall_accuracy, macro_precision, macro_recall, macro_f1
220220
end
221221

222-
function change_array_type(original_array::Array{Union{Float64, Int64, String}, 2})::Array{Union{Float64, String}, 2}
223-
original_array = original_array[:, 1:end-1]
224-
converted_array = Array{Union{Float64, String}, 2}(undef, size(original_array))
225-
226-
for i in 1:size(original_array, 1)
227-
for j in 1:size(original_array, 2)
228-
element = original_array[i, j]
229-
if element isa Int
230-
# Convert Int64 to Float64
231-
converted_array[i, j] = float(element)
232-
else
233-
# Keep Float64 and String as they are
234-
converted_array[i, j] = element
235-
end
236-
end
237-
end
238-
return converted_array
239-
end
240-
241-
function class_inference(seed::Int64, ucs::UCS, state::Vector{Union{Float64, String}})::Int64
222+
function class_inference(seed::Int64, ucs::UCS, state::Vector{Union{Float64, Int64, String}})::Int64
242223
match_set::Vector{Classifier} = @views generate_match_set(ucs, state, -1, true)
243224
fitness_sum_array::Vector{Float64} = @views generate_fitness_sum_array(ucs, match_set)
244225
action::Int64 = random_max_index(seed, fitness_sum_array) - 1

ucs/main.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using ArgParse, Random, CSV, DataFrames, Dates
22

33
function parse_commandline()
4-
s = ArgParseSettings(description="UCS Classifier System")
4+
s = ArgParseSettings(description="The UCS classifier system")
55
@add_arg_table s begin
66
"--num_trials"
77
help = "The number of trials"
@@ -150,8 +150,8 @@ function main_csv(args; now_str=Dates.format(Dates.now(), "Y-m-d-H-M-S"))
150150

151151
# Test Phase
152152
env.is_exploit = true
153-
train_accuracy::Float64, train_precision::Float64, train_recall::Float64, train_f1::Float64 = get_scores_per_epoch(env.seed, ucs, env.train_data, change_array_type(env.train_data))
154-
test_accuracy::Float64, test_precision::Float64, test_recall::Float64, test_f1::Float64 = get_scores_per_epoch(env.seed, ucs, env.test_data, change_array_type(env.test_data))
153+
train_accuracy::Float64, train_precision::Float64, train_recall::Float64, train_f1::Float64 = get_scores_per_epoch(env.seed, ucs, env.train_data)
154+
test_accuracy::Float64, test_precision::Float64, test_recall::Float64, test_f1::Float64 = get_scores_per_epoch(env.seed, ucs, env.test_data)
155155
popsize::Int64 = length(ucs.population)
156156

157157
# Output log
@@ -213,7 +213,7 @@ function main_all_csv(args)
213213
"wpbc",
214214
"yeast",
215215

216-
# 7 datasets used in the supplementary material
216+
# 7 datasets used in the appendix
217217
"12carry",
218218
"11mop",
219219
"11mux",

0 commit comments

Comments
 (0)