Fuzzy Control and Decision Making
This section discusses how fuzzy logic can be applied to control systems or decision-making architectures. It's simple to understand why the applications are so successful when the technique is so close to rule-based systems (the single most successful AI technique, as discussed in Chapter 11, "Rule-Based Systems").
Isn't fuzzy control an oxymoron? Can it work at all? Of course. Humans do not require precise numerical information, yet they are capable of highly adaptive control. It seems obvious to try to program controllers to accept imprecise and noisy input. Then, the system may be able to deal with complex situations in a smarter fashion, and may be easier to implement. Decision making benefits from the same advantage: human-like reasoning on concepts with degrees of truth.
In fuzzy systems, there is also a working memory. Naturally, the symbols are not crisp values but fuzzy ones. Apart from the obvious fuzziness, there is little distinction between a working memory in a fuzzy system and a standard rule-base system. In fact, in most implementations, the same generic container could be used to store the fuzzy variables.
Just like rule-based systems, fuzzy systems rely on a knowledge base, containing a set of rules. Instead of being defined in terms of crisp symbols, fuzzy variables are used instead (see Table 30.5). Unconfident, vulnerable, attack, and retreat are fuzzy variables. Enemy and accuracy are linguistic variables, with healthy/damaged/dying and high/medium/low as fuzzy terms.
Despite these additions, the acquisition of knowledge is slightly simpler with fuzzy systems. Indeed, fuzzy rules are much closer to human knowledge, which can reduce the number of total rules required. The linguistic expressions and modifiers are also very intuitive to work with, so almost anyone can add rules to the system.
The major problem is creating the membership functions for each of the fuzzy variables. The membership functions have a tremendous effect on the output of the system, so it's important to get it right. Different approaches can be used to create membership functions:
The interpreter is responsible for manipulating the variables in the working memory using the rules. There are two key differences compared with crisp rule-based systems: the matching and the evaluation of rules. So almost everything changes!
In fuzzy logic, true and false are just special cases. Most of the time, fuzzy values will have partial membership to sets. This is not a problem in itself; the fuzzy rules are meant to have fuzzy inputs.
The problem is that we cannot discard most of the rules. Even fuzzy values with small degrees of truth will need to be plugged into the relevant rules. In practice, most fuzzy values become nonzero after a short amount of execution. So almost every rule has to be checked!
The best way to deal with this is prevention. We can make sure there aren't that many rules in the system! There have been some suggestions to deal with the combinatorial explosion of rules by reorganizing the rules, but these have turned out mathematically inaccurate and only applicable in a few cases.
So it seems the only way to deal with many fuzzy rules is to approximate the computation, to discard rules as in crisp rule-based systems. To do this, the interpreter can just ignore fuzzy values below a certain threshold (considering them false). For example, all the fuzzy values below 0.5 can be ignored, and values above 0.5 can be rescaled to fit the [0,1] range. This can be seen as trimming all the membership functions, but dealing with this in the interpreter instead will be more flexible and no slower. The higher the threshold, the closer the system is to a crisp rule-based system.
Inference is a process applied to each rule. Essentially, the purpose is to determine the degree of truth of the THEN part of the rule, based on the IF part. This can be achieved using fuzzy logic; the appropriate fuzzy operators can be used to reduce any linguistic expression into a single fuzzy value.
The resulting fuzzy value can be considered as the output of this rule. However, the output is also associated with a fuzzy set—as all other fuzzy variables. This set will only be used for defuzzification. There are two different ways to compute the fuzzy output set (see Figure 30.7):
Figure 30.7. Example of fuzzy inference using the product method to scale the output set. The actual fuzzy values are denoted underneath the membership functions.
In the implementation, there's usually no need to manipulate any fuzzy sets. The two different operations—MIN and PRODUCT—can be understood as an interpretation the output value and set. As such, we can take this interpretation into account during the defuzzification; this is lazy evaluation.
Many rules may have the same fuzzy variable in the body of the rule. The composition stage must take into account each consequent clause that affects a single variable, and combine them together (see Figure 30.8). Again, there are two common ways to do this:
Again, the composition can be interpreted as having two outputs: the actual fuzzy value, and a corresponding fuzzy set. The set itself is only used when defuzzifying, but the fuzzy value is reinserted into the working memory.
The simulation of the fuzzy system requires two copies of the working memory to be consistent. One stores the current results, and the other remembers the previous state (until the update is finished). This is necessary because updating the same working memory leads to somewhat unpredictable results, which depend on the order of processing.
All the rules are processed as shown in Listing 30.1. The system could discard some rules where the antecedent clause is exactly 0. Doing this efficiently requires a treelike structure, as discussed for the rule-based systems. For fuzzy systems, this trick will not pay off because the values are rarely 0.
current is the working memory with the latest fuzzy variables previous is the last state of the working memory for each value in the working memory previous[value] = current[value] current[value] = 0 end for for each rule result = evaluate( rule.expression, previous ) current[rule.body] = MAX( current[rule.body], result ) end for