2024/6/17

gradio

gradio 是一組 python library,可快速建立網頁介面的 API,目的是提供給 Machine Learning Model demo 的使用者操作介面。大部分的 AI model 是以 python 開發,且使用程式或文字介面操作,gradio 可快速將使用介面轉換為網頁應用程式,不需要撰寫網頁 css, javascript 等程式碼。

Quickstart 是 gradio 的快速入門說明

安裝 gradio

pip3 install gradio

在執行時,遇到這樣的錯誤訊息

ImportError: urllib3 v2 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'OpenSSL 1.0.2k-fips  26 Jan 2017'. See: https://github.com/urllib3/urllib3/issues/2168

根據 python - ImportError: urllib3 v2.0 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with LibreSSL 2.8.3 - Stack Overflow 的說明,要 downgrade urllib3

pip3 uninstall urllib3
pip3 install 'urllib3<2.0'

test1.py,launch 時加上 server_name="0.0.0.0" 參數,可讓機器所有 network interfaces 在 TCP Port 7860 提供網頁服務

import gradio as gr

def greet(name, intensity):
    return "Hello " * intensity + name + "!"

demo = gr.Interface(
    fn=greet,
    inputs=["text", "slider"],
    outputs=["text"],
)

demo.launch(server_name="0.0.0.0")

在瀏覽器連接 http://localhost:7860

如果在 launch 加上參數 share=True

啟動時,會出現這樣的訊息

$ python3 test1.py
Running on local URL:  http://127.0.0.1:7860
Running on public URL: https://424222ce32aa52ca72.gradio.live

This share link expires in 72 hours. For free permanent hosting and GPU upgrades, run `gradio deploy` from Terminal to deploy to Spaces (https://huggingface.co/spaces)

這表示可透過一個 public redirect 的網址,連接到這個 service

References

gradio

Creating A Chatbot Fast

2024/6/3

log4j2

java 開發環境,JDK 預設有提供 logging 機制,但 logging 機制的發展,也是一段精彩的歷史。就以往使用的經驗,從一開始的 log4j,後來換成了 logback + slf4j,到現在這個階段,似乎又要換成 log4j2,因為 log4j2 的效能評估,結果已經是又超過了 logback。

logging framwork 的發展可以參考這一篇文章: Java Logging 的歷史與戰爭

但這種情況也說明了,開放且多元的 java 開發環境,一個必要且熱門的 framework,會吸引大家注意並不斷提出更好的框架,但使用者就得跟著時代不斷的進步。

使用 log4j 用 maven 引用 library,在 pom.xml 的 dependencies 裡面加上 log4j2 及 slf4j。

slf4j 是 logging 的 wrapper,可用一致 API 介面進行程式開發,在不修改程式的狀況下,可替換內部的實作為 logback 或 log4j 或 jdk logging。

    <dependencies>
        <!-- log4j2 + slf4j -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.22.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.22.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.22.1</version>
        </dependency>
    </dependencies>

在 classpath 裡面加上設定檔 log4j2.xml

以下是調整後的設定

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout>
                <Pattern>%d{HH:mm:ss.SSS} [%thread] %-5p %c %L - %m%n</Pattern>
            </PatternLayout>
        </Console>

        <!--
        <File name="FILE" fileName="testweb.log">
            <PatternLayout>
                <Pattern>%d [%thread] %-5p %c %L - %m%n</Pattern>
            </PatternLayout>
        </File>
        -->
        <RollingFile name="RollingFile" fileName="logs/testweb.log" filePattern="logs/testweb-%d{yyyy-MM-dd}-%i.log.gz">
            <PatternLayout>
                <Pattern>%d [%thread] %-5p %c %L - %m%n</Pattern>
            </PatternLayout>
            <Policies>
                <OnStartupTriggeringPolicy />
                <TimeBasedTriggeringPolicy />
                <SizeBasedTriggeringPolicy size="250 MB"/>
            </Policies>
            <DefaultRolloverStrategy>
                <Delete basePath="${baseDir}" maxDepth="2">
                    <IfFileName glob="*/test-*.log.gz" />
                    <IfLastModified age="30d" />
                </Delete>
            </DefaultRolloverStrategy>
        </RollingFile>
    </Appenders>
    <Loggers>
        <!-- hibernate -->
        <logger name="org.hibernate" level="INFO" />
        <Logger name="org.hibernate.SQL" level="DEBUG" />
        <logger name="org.hibernate.cfg" level="ERROR" />

        <Root level="info">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="RollingFile"/>
        </Root>
    </Loggers>
</Configuration>

RollingFile 的部分可以參考官方說明Log4j 2 Appenders

Policies 的部分,決定什麼時候要 trigger rolling 的機制。OnStartupTriggeringPolicy 代表程式啟動要做一次。TimeBasedTriggeringPolicy 則是跟著 Pattern 裡面日期的最小精度的部分 trigger,目前日期是設定為日,所以這個範例,每天會處理一次。SizeBasedTriggeringPolicy 則是會判斷 log file 的檔案大小 trigger,pattern 部分會因為 %i 這個設定,每次會將檔案改為 -1, -2 這樣的檔名。

DefaultRolloverStrategy 可將超過 30 天,符合glob 設定的 pattern 的檔案刪除。

使用 logging 是透過 sl4j 介面

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorld {
    Logger log = LoggerFactory.getLogger(this.getClass().getName());
    public void sayHello(){
        log.debug("This will be printed on debug");
        log.info("This will be printed on info");
        log.warn("This will be printed on warn");
        log.error("This will be printed on error");

        log.info("Appending string: {}.", "Hello, World");
    }
}

log4j2 對效能最大的改進點在非同步的 file appender Log4j 2 Appenders

調整 log4j2.xml 設定,基於上面的 RollingFile 增加 Appenders。

bufferSize 預設為 1024,代表可暫存 1024 則 log,必須要設定為 2 的指數。

        <Async name="AsyncRollingFile" bufferSize="262144">
            <AppenderRef ref="RollingFile"/>
        </Async>

然後修改下面的 AppenderRef,就可以換成 AsyncRollingFile

        <Root level="info">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="AsyncRollingFile"/>
        </Root>

References

Log4j2中RollingFile的文件滚动更新机制 - Ye_yang - 博客园

Java Logging Frameworks: log4j vs logback vs log4j2

Log4j; Performance

2024/5/27

Guava Reflection

TypeToken

因為 java 有 type erasure 特性,無法在 runtime 得知 generic Class 物件

TypeToken 使用了 workaround 方法,可處理 generict types,就是能夠在 runtime 檢測 Generic Type

    @Test
    public void typeToken() {
        ArrayList<String> stringList = Lists.newArrayList();
        ArrayList<Integer> intList = Lists.newArrayList();
        System.out.println(stringList.getClass().isAssignableFrom(intList.getClass()));
        // returns true, even though ArrayList<String> is not assignable from ArrayList<Integer>

        TypeToken<List<String>> stringListToken = new TypeToken<List<String>>() {};
        TypeToken<List<Integer>> integerListToken = new TypeToken<List<Integer>>() {};
        TypeToken<List<? extends Number>> numberTypeToken = new TypeToken<List<? extends Number>>() {};
        assertFalse(stringListToken.isSubtypeOf(integerListToken));
        assertFalse(numberTypeToken.isSubtypeOf(integerListToken));
        assertTrue(integerListToken.isSubtypeOf(numberTypeToken));
    }

利用 TypeToken 在 runtime 取得 complex type 資訊

    abstract class ParametrizedClass<T> {
        TypeToken<T> type = new TypeToken<T>(getClass()) {};
    }
    @Test public void typeToken2() throws NoSuchMethodException {
        //// 產生一個 class 裡面儲存了 TypeToken 欄位
        ParametrizedClass<String> parametrizedClass = new ParametrizedClass<String>() {};
        assertEquals(parametrizedClass.type, TypeToken.of(String.class));

        //// 也能產生 TypeToken of a complex type,裡面有多個 generic type
        // runtime 還是能取得 type 資訊
        TypeToken<Function<Integer, String>> funToken
                = new TypeToken<Function<Integer, String>>() {};
        TypeToken<?> funResultToken = funToken
                .resolveType(Function.class.getTypeParameters()[1]);
        assertEquals(funResultToken, TypeToken.of(String.class));

        /// 也能在 Map 裡面得知 entry 的 data type
        TypeToken<Map<String, Integer>> mapToken
                = new TypeToken<Map<String, Integer>>() {};
        TypeToken<?> entrySetToken = mapToken
                .resolveType(Map.class.getMethod("entrySet")
                        .getGenericReturnType());
        assertEquals(
                entrySetToken,
                new TypeToken<Set<Map.Entry<String, Integer>>>() {});
    }

Invokable

Invokable 是 java.lang.reflec.Method 及 java.lang.reflect.Constructor 的 fluent wrapper,提供簡化後的 API

    @Test
    public void invokable() throws NoSuchMethodException {
        Method method = CustomClass.class.getMethod("somePublicMethod");
        Invokable<CustomClass, ?> invokable
                = new TypeToken<CustomClass>() {}
                .method(method);
        // 分別透過 Guava 及 標準的 JAVA reflection API 檢測是否有 somePublicMethod 這個 method
        boolean isPublicStandradJava = Modifier.isPublic(method.getModifiers());
        boolean isPublicGuava = invokable.isPublic();

        assertTrue(isPublicStandradJava);
        assertTrue(isPublicGuava);

        ////////
        // checking if a method is overridable
        Method method2 = CustomClass.class.getMethod("notOverridablePublicMethod");
        // 用 Guava TypeToken 比較簡單
        Invokable<CustomClass, ?> invokable2
                = new TypeToken<CustomClass>() {}.method(method2);

        // 用標準 java 的方法
        boolean isOverridableStandardJava = (!(Modifier.isFinal(method2.getModifiers())
                || Modifier.isPrivate(method2.getModifiers())
                || Modifier.isStatic(method2.getModifiers())
                || Modifier.isFinal(method2.getDeclaringClass().getModifiers())));
        boolean isOverridableFinalGauava = invokable2.isOverridable();

        assertFalse(isOverridableStandardJava);
        assertFalse(isOverridableFinalGauava);
    }

References

Guide to Guava's Reflection Utilities | Baeldung

ReflectionExplained · google/guava Wiki · GitHub

2024/5/20

Guava Math

Guava 針對 int, long, BigIntegers, double 提供一些 Math Utility Class methods

IntMath

    @Test
    public void intMath() {
        // binomial(int n, int k)
        // binomial coefficient of n and k
        // 計算 n/k(n-k)
        int result = IntMath.binomial(6, 3);
        assertEquals(20, result);
        // 超過 Integer range 時,會回傳 MAX_VALUE
        result = IntMath.binomial(Integer.MAX_VALUE, 3);
        assertEquals(Integer.MAX_VALUE, result);

        //// ceilingPowerOfTwo(int x)
        // the smallest power of two which is greater than or equal to x
        // x 落在 2 的哪一個指數結果的區間
        // 2^(n-1) < x < 2 ^n
        result = IntMath.ceilingPowerOfTwo(20);
        assertEquals(32, result);

        //// checkedAdd(int a, int b)
        // a+b ,同時會檢查是否結果有 overflow,發生時會 throw ArithmeticException
        result = IntMath.checkedAdd(1, 2);
        assertEquals(3, result);

        Exception exception = assertThrows(ArithmeticException.class, () -> {
            IntMath.checkedAdd(Integer.MAX_VALUE, 100);
        });

        /// divide(int p, int q, RoundingMode mode)
        // 定義 rounding mode 的除法
        result = IntMath.divide(10, 3, RoundingMode.CEILING);
        assertEquals(4, result);
        Exception exception2 = assertThrows(ArithmeticException.class, () -> {
            // 未定義 Rounding Mode  但實際上結果需要 rounding
            IntMath.divide(10, 3, RoundingMode.UNNECESSARY);
        });

        /// factorial(int n)
        // n!   n 階層
        result = IntMath.factorial(5);
        assertEquals(120, result);
        result = IntMath.factorial(Integer.MAX_VALUE);
        assertEquals(Integer.MAX_VALUE, result);

        //// floorPowerOfTwo(int x)
        // 2^n < x < 2 ^(n+1)
        result = IntMath.floorPowerOfTwo(30);
        assertEquals(16, result);

        //// gcd(int a, int b)
        // 最大公因數
        result = IntMath.gcd(30, 40);
        assertEquals(10, result);

        //// isPowerOfTwo(int x)
        // 是否為 2 的倍數
        assertTrue( IntMath.isPowerOfTwo(16) );
        assertFalse( IntMath.isPowerOfTwo(20) );

        //// isPrime(int n)
        // 是否為質數
        assertFalse( IntMath.isPrime(20) );

        //// log10(int x, RoundingMode mode)
        // 以 10 為底 的對數
        result = IntMath.log10(30, RoundingMode.CEILING);
        assertEquals(2, result);
        Exception exception3 = assertThrows(ArithmeticException.class, () -> {
            // 未定義 Rounding Mode  但實際上結果需要 rounding
            IntMath.log10(30, RoundingMode.UNNECESSARY);
        });

        //// log2(int x, RoundingMode mode)
        // 以 2 為底 的對數
        result = IntMath.log2(30, RoundingMode.CEILING);
        assertEquals(5, result);
        Exception exception4 = assertThrows(ArithmeticException.class, () -> {
            // 未定義 Rounding Mode  但實際上結果需要 rounding
            IntMath.log2(30, RoundingMode.UNNECESSARY);
        });

        //// mean(int x, int y)
        //平均
        result = IntMath.mean(30, 20);
        assertEquals(25, result);

        // mod(int x, int m)
        // 餘數
        result = IntMath.mod(30, 4);
        assertEquals(2, result);

        // pow(int b, int k)
        // 指數
        result = IntMath.pow(6, 4);
        assertEquals(1296, result);

        //// saturatedAdd(int a, int b)
        // 控制 overflow/ underflow 時,會改回傳 MAX_VALUE/MIN_VALUE
        result = IntMath.saturatedAdd(6, 4);
        assertEquals(10, result);
        result = IntMath.saturatedAdd(Integer.MAX_VALUE, 1000);
        assertEquals(Integer.MAX_VALUE, result);

        //// sqrt(int x, RoundingMode mode)
        // 平方根
        result = IntMath.sqrt(30, RoundingMode.CEILING);
        assertEquals(6, result);
        Exception exception5 = assertThrows(ArithmeticException.class, () -> {
            // 未定義 Rounding Mode  但實際上結果需要 rounding
            IntMath.sqrt(30, RoundingMode.UNNECESSARY);
        });

    }

LongMath

大部分跟 IntMath 一樣,除了 mod

    @Test
    public void longMath() {
        // Most operations are similar to the IntMath utility
        // 以下是不同的地方

        /// mod(long x, int m) and mod(long x, long m)
        // x mod m
        int result = LongMath.mod(30L, 4);
        assertEquals(2, result);
        long result2 = LongMath.mod(30L, 4L);
        assertEquals(2L, result);
    }

BigIntegerMath

跟 IntMath 類似

DoubleMath

    @Test
    public void doubleMath() {
        //// isMathematicalInteger(double x)
        // 是否為整數
        boolean result = DoubleMath.isMathematicalInteger(5);
        assertTrue(result);
        result = DoubleMath.isMathematicalInteger(5.2);
        assertFalse(result);


        //// log2(double x)
        double result2 = DoubleMath.log2(4);
        assertEquals(2, result2, 0);
    }

References

MathExplained · google/guava Wiki · GitHub

Guide to Mathematical Utilities in Guava | Baeldung

2024/5/13

Guava Hashing

Guava Hash 裡面有幾個重要的類別:Hashing, HashFunction, Hasher, HashCode, Funnel, PrimitiveSink

HashFunction

HashFunction 是 stateless function,可將任意的 data 轉換為 fixed number of bits,相同的 input 會得到相同的 output 結果,不同的 input 得到不同的唯一的結果

可產生 Hasher 物件,也可以根據條件回傳 HashCode 結果

    @Test
    public void hashFunction() {
        HashFunction hf = Hashing.md5();
        HashCode hc = hf.newHasher()
                .putLong(10)
                .putString("abc", Charsets.UTF_8)
                .hash();
        System.out.println("hashFunction="+hc.toString());
    }

Hasher

HashFunction 可以使用 stateful Hasher,Hasher 提供 fluent syntax,可將資料填充到 Hash 裡面,並得到 hash value。Hasher 可接受 primitive input, byte array, slices of byte arrays, character sequences, 任意物件產生的 Funnel。

資料透過 putXXX() method 放到 Hasher 的資料源中,有 putByte, putBytes, putShort, putInt, putLong, putFloat, putdouble, putBoolean, putChar, putEnencodedChars, putString, putObject

Funnel

如果有一個類別

class Person {
  final int id;
  final String firstName;
  final String lastName;
  final int birthYear;
}

其 Funnel 會是

Funnel<Person> personFunnel = new Funnel<Person>() {
  @Override
  public void funnel(Person person, PrimitiveSink into) {
    into
        .putInt(person.id)
        .putString(person.firstName, Charsets.UTF_8)
        .putString(person.lastName, Charsets.UTF_8)
        .putInt(birthYear);
  }
};

組合起來的code

    @Test
    public void objectToHashCode() {

        // 需要hash的对象
        Person person = new Person(27, "John", "Wink", 1990);

        Funnel<Person> personFunnel = new Funnel<Person>() {
            @Override
            public void funnel(Person person, PrimitiveSink into) {
                into
                        .putInt(person.id)
                        .putString(person.firstName, Charsets.UTF_8)
                        .putString(person.lastName, Charsets.UTF_8)
                        .putInt(person.birthYear);
            }
        };

        // md5算法
        HashFunction hf = Hashing.md5();
        HashCode hc = hf.newHasher()
                .putString("abc", Charsets.UTF_8)
                .putObject(person, personFunnel)
                .hash();
        System.out.println("objectToHashCode="+hc.toString());
    }

    class Person {
        int id;
        String firstName;
        String lastName;
        int birthYear;

        public Person(int id, String firstName, String lastName, int birthYear) {
            this.id = id;
            this.firstName = firstName;
            this.lastName = lastName;
            this.birthYear = birthYear;
        }
    }

HashCode

當 Hasher 給了所有 input 資料,他的 hash() method 會產生 HashCode 結果

HashCode 支援 euqality testing 例如 asInt(), asLong, asBytes()

另外有 writeBytesTo(array, offset, maxLength) 可將 hash 的 maxLength bytes 寫入 array

BloomFilter

可偵測物件是否 "definitely" 不在 filter 裡面,或是有可能已經被加入 Bloom filter

對於 hash 來說,就是偵測是否 hash value 有碰撞,不同的 input 得到相同的結果

BloomFilter<Person> friends = BloomFilter.create(personFunnel, 500, 0.01);
for (Person friend : friendsList) {
  friends.put(friend);
}
// much later
if (friends.mightContain(someone)) {
  // the probability that someone reached this place if they aren't a friend is
  // 1% we might, for example, start asynchronously loading things for someone
  // while we do a more expensive exact check
}

Hashing

Hashing (Guava: Google Core Libraries for Java HEAD-jre-SNAPSHOT API)

裡面有 static method,可取得某些演算法的 HashFunction

  • adler32()

  • crc32(), crc32c()

  • farmHashFingerprint64()

  • fingerprint2011()

  • goodFastHash(int minimumBits)

  • hmacMd5

  • hmacSha1

  • hmacSha256

  • hmacSha512

  • md5()

  • murmur3_128(), murmur3_32()

  • sha1(), sha256(), sha384(), sha512()

  • sipHash24

    @Test
    public void hashing() {
        // 測試 Hasing 演算法
        String input = "hello, world";
        // MD5
        System.out.println("md5="+Hashing.md5().hashBytes(input.getBytes()).toString());
        // sha256
        System.out.println("sha256="+Hashing.sha256().hashBytes(input.getBytes()).toString());
        // sha512
        System.out.println("sha512="+Hashing.sha512().hashBytes(input.getBytes()).toString());
        // crc32
        System.out.println("crc32="+Hashing.crc32().hashBytes(input.getBytes()).toString());
        // MD5
        System.out.println("MD5="+Hashing.md5().hashUnencodedChars(input).toString());
    }

References

HashingExplained · google/guava Wiki · GitHub