Categories PHP-MySQL Vietnamese names

Hàm PHP đơn giản giúp xác định giới tính của tên

Hôm nọ tôi có viết bài mô hình xác định giới tính của đệm tên, trong đó có nói sơ về thuật toán, và mấy mảng chỉ số phân biệt giới để dùng phân tích dữ liệu đầu vào. Hôm nay, tôi viết hàm PHP đơn giản để xử lý vấn đề đó, nó đây:

Trước hết là 2 hàm nhỏ hỗ trợ:

<?php

/* 
 * PHP function to determine gender of the name
 * MIT License
 * Nguyen Duc Anh - freehost.page
 */


// bỏ khoảng trắng dư thừa
function vn_rmv_wsp($str) { 
    $str2 = trim($str, ' '); 
    $rs = preg_replace('/\s+/', ' ', $str2); 

return $rs;
}


// số lượng từ của chuỗi
function vnn_word_count($str) { 
    $str2 = vn_rmv_wsp($str); 
    $word = mb_split(' ', $str2); 
    $rs = count($word); 

return $rs;
}

Sau đó là hàm chính:

<?php

/* 
 * PHP function to determine gender of the name
 * MIT License
 * Nguyen Duc Anh - freehost.page
 */

function vn_gender_of_name_detect($name) {
// Đối với 100 đệm nữ phổ biến nhất, chỉ số phân biệt giới, nam / nữ

$mid_female_pop_ratio = array(
        "bảo 0.2906",
        "ngọc 0.2377",
        "thanh 0.6048",
        "phương 0.0668",
        "minh 2.3654",
        "kim 0.08",
        "khánh 0.2814",
        "quỳnh 0.005",
        "gia 2.3184",
        "như 0.0363",
        "anh 1.7481",
        "thảo 0.0064",
        "mỹ 0.0067",
        "yến 0.0047",
        "hồng 0.3364",
        "thùy 0.007",
        "tường 0.0623",
        "hoàng 4.1042",
        "thiên 1.3579",
        "tuyết 0.0055",
        "thu 0.0105",
        "mai 0.0224",
        "xuân 0.7826",
        "trúc 0.014",
        "thúy 0.0024",
        "bích 0.0104",
        "hà 0.0801",
        "ánh 0.0198",
        "hải 1.2511",
        "nhã 0.0108",
        "kiều 0.0104",
        "cẩm 0.1481",
        "diễm 0.0105",
        "lan 0.0066",
        "tú 0.0443",
        "vân 0.0426",
        "thủy 0.0144",
        "trâm 0.005",
        "trà 0.0085",
        "thị 0.001",
        "huỳnh 0.4075",
        "uyên 0.0107",
        "hoài 1.2162",
        "nhật 5.1423",
        "cát 0.0608",
        "tâm 0.1695",
        "huyền 0.0152",
        "hương 0.018",
        "linh 0.0706",
        "khả 0.1092",
        "ái 0.0177",
        "an 1.0206",
        "diệu 0.1006",
        "ngân 0.037",
        "thục 0.0106",
        "quế 0.0538",
        "kỳ 0.7218",
        "tuệ 0.0798",
        "đan 0.166",
        "thái 4.7217",
        "tố 0.0132",
        "lê 2.4273",
        "bội 0.0321",
        "đông 0.8571",
        "phi 2.4523",
        "hạnh 0.0452",
        "uyển 0.001",
        "song 0.2656",
        "nam 2.6087",
        "huệ 0.0511",
        "nguyệt 0.0116",
        "ý 0.0545",
        "mẫn 0.122",
        "nguyên 5.2",
        "phúc 10.3974",
        "châu 0.4685",
        "trang 0.0315",
        "lam 0.121",
        "tiểu 0.2213",
        "bình 2.6083",
        "hiền 0.2569",
        "lệ 0.0093",
        "băng 0.0093",
        "mộng 0.001",
        "đoan 0.001",
        "triệu 1.2083",
        "hiểu 0.383",
        "việt 7.8696",
        "thư 0.0112",
        "vy 0.046",
        "hạ 0.0247",
        "lâm 2.3026",
        "thụy 0.04",
        "hiếu 4.3562",
        "khải 3.411",
        "phụng 0.0704",
        "diệp 0.2143",
        "thy 0.0597",
        "khiết 0.1833",
        "hân 0.0536"
);

// Đối với 100 đệm nam phổ biến nhất, chỉ số phân biệt giới, nam / nữ

$mid_male_pop_ratio = array(
        "minh 2.3654",
        "gia 2.3184",
        "hoàng 4.1042",
        "quốc 256.913",
        "anh 1.7481",
        "thanh 0.6048",
        "thành 328.9167",
        "tuấn 377.2",
        "tấn 438.375",
        "đức 157.1579",
        "quang 218.6154",
        "văn 394.1429",
        "bảo 0.2906",
        "nhật 5.1423",
        "đăng 81.5667",
        "duy 56.55",
        "thiên 1.3579",
        "ngọc 0.2377",
        "trung 96.15",
        "hữu 136.9286",
        "trọng 231.5",
        "phúc 10.3974",
        "tiến 506.6667",
        "chí 138.1",
        "khánh 0.2814",
        "hải 1.2511",
        "huy 97.1667",
        "đình 34.9091",
        "xuân 0.7826",
        "thái 4.7217",
        "công 177.8333",
        "trí 110.6667",
        "thế 100.8889",
        "phước 18.5217",
        "phú 49.6471",
        "hồng 0.3364",
        "nguyên 5.2",
        "trường 15.5417",
        "việt 7.8696",
        "vĩnh 17.125",
        "hoài 1.2162",
        "mạnh 108.1667",
        "thiện 14.45",
        "lê 2.4273",
        "phi 2.4523",
        "nam 2.6087",
        "phương 0.0668",
        "bá 221.5",
        "đại 103.5",
        "an 1.0206",
        "kim 0.08",
        "khôi 17.1429",
        "kiến 48.7143",
        "hiếu 4.3562",
        "nhựt 12.0385",
        "bình 2.6083",
        "cao 24",
        "vũ 16.8824",
        "hùng 84",
        "khải 3.411",
        "chấn 247",
        "huỳnh 0.4075",
        "viết 43.6",
        "hưng 194",
        "tùng 5.3889",
        "đông 0.8571",
        "phát 180",
        "kỳ 0.7218",
        "hạo 14.8333",
        "long 25.2857",
        "nhất 11",
        "lâm 2.3026",
        "vĩ 32.2",
        "thuận 7.85",
        "khang 7.8421",
        "vinh 24.6667",
        "sơn 4.3333",
        "nguyễn 6.7143",
        "quý 3.2857",
        "khắc 32.75",
        "trần 21.5",
        "cẩm 0.1481",
        "sỹ 119",
        "nhân 6.5",
        "triệu 1.2083",
        "như 0.0363",
        "tuần 1000",
        "tường 0.0623",
        "phong 20",
        "tần 1000",
        "dương 6.125",
        "đắc 1000",
        "hào 43",
        "danh 17",
        "triều 3.4",
        "hà 0.0801",
        "tâm 0.1695",
        "hòa 7.8",
        "sĩ 35.5",
        "hoàn 1.4792"
);

// Đối với 100 tên nữ phổ biến nhất, chỉ số phân biệt giới nam / nữ

$forename_female_pop_ratio = array(
        "anh 0.4168",
        "vy 0.0071",
        "ngọc 0.0349",
        "nhi 0.006",
        "hân 0.0106",
        "thư 0.0063",
        "linh 0.0434",
        "như 0.0055",
        "ngân 0.0095",
        "phương 0.1652",
        "thảo 0.0291",
        "my 0.0061",
        "trân 0.0061",
        "quỳnh 0.0188",
        "nghi 0.0256",
        "trang 0.0066",
        "trâm 0.0078",
        "an 0.8035",
        "thy 0.0079",
        "châu 0.0713",
        "trúc 0.0128",
        "uyên 0.0019",
        "yến 0.0057",
        "ý 0.0234",
        "tiên 0.0154",
        "mai 0.0085",
        "hà 0.0871",
        "vân 0.0131",
        "nguyên 1.4339",
        "hương 0.0078",
        "quyên 0.0099",
        "duyên 0.0078",
        "kim 0.0488",
        "trinh 0.0057",
        "thanh 0.4069",
        "tuyền 0.0179",
        "hằng 0.0108",
        "dương 0.6413",
        "chi 0.0175",
        "giang 0.3136",
        "tâm 1.1008",
        "lam 0.0492",
        "tú 1.306",
        "ánh 0.0299",
        "hiền 0.2148",
        "khánh 2.0549",
        "minh 6.0777",
        "huyền 0.0067",
        "thùy 0.0035",
        "vi 0.0165",
        "ly 0.0095",
        "dung 0.002",
        "nhung 0.002",
        "phúc 7.5168",
        "lan 0.0064",
        "phụng 0.0783",
        "ân 1.7271",
        "thi 0.0827",
        "khanh 0.6532",
        "kỳ 0.5221",
        "nga 0.005",
        "tường 0.8272",
        "thúy 0.0028",
        "mỹ 0.1194",
        "hoa 0.0335",
        "tuyết 0.0028",
        "lâm 2.7394",
        "thủy 0.0291",
        "đan 0.1518",
        "hạnh 0.0393",
        "xuân 0.0634",
        "oanh 0.0121",
        "mẫn 0.4114",
        "khuê 0.1151",
        "diệp 0.0299",
        "thương 0.1706",
        "nhiên 0.274",
        "băng 0.0246",
        "hồng 0.084",
        "bình 2.8223",
        "loan 0.0043",
        "thơ 0.0086",
        "phượng 0.0181",
        "mi 0.0046",
        "nhã 0.1981",
        "nguyệt 0.001",
        "bích 0.0105",
        "đào 0.037",
        "diễm 0.001",
        "kiều 0.0233",
        "hiếu 11.1813",
        "di 0.1282",
        "liên 0.001",
        "trà 0.071",
        "tuệ 0.2895",
        "thắm 0.001",
        "diệu 0.0435",
        "quân 15.8971",
        "nhàn 0.1691",
        "doanh 0.2647"
);


// Đối với 100 tên nam phổ biến nhất, chỉ số phân biệt giới, nam / nữ

$forename_male_pop_ratio = array(
        "huy 363.5882",
        "khang 164.7188",
        "bảo 65.2436",
        "minh 6.0777",
        "phúc 7.5168",
        "anh 0.4168",
        "khoa 93.6471",
        "phát 313",
        "đạt 311.7",
        "khôi 42.6984",
        "long 356.8571",
        "nam 130.6667",
        "duy 51",
        "quân 15.8971",
        "kiệt 1042.5",
        "thịnh 131.0667",
        "tuấn 270.1429",
        "hưng 156.6667",
        "hoàng 23.2278",
        "hiếu 11.1813",
        "nhân 41.381",
        "trí 284.8333",
        "tài 238.7143",
        "phong 396",
        "nguyên 1.4339",
        "an 0.8035",
        "phú 89.8235",
        "thành 121.5833",
        "đức 175.75",
        "dũng 341.25",
        "lộc 27.8511",
        "khánh 2.0549",
        "vinh 243.2",
        "tiến 119.1",
        "nghĩa 93.6667",
        "thiện 34.6875",
        "hào 272.25",
        "hải 54.9474",
        "đăng 69",
        "quang 333.3333",
        "lâm 2.7394",
        "nhật 41.087",
        "trung 130.1429",
        "thắng 296.3333",
        "tú 1.306",
        "hùng 411",
        "tâm 1.1008",
        "sang 12.6032",
        "sơn 131.8333",
        "thái 131.3333",
        "cường 196",
        "vũ 86.6667",
        "toàn 193.25",
        "ân 1.7271",
        "thuận 15.9333",
        "bình 2.8223",
        "trường 1000",
        "danh 69.8889",
        "kiên 306.5",
        "phước 30",
        "thiên 10.6226",
        "tân 56.2",
        "việt 53.6",
        "khải 74.7143",
        "tín 254.5",
        "dương 0.6413",
        "tùng 252",
        "quý 9.5294",
        "hậu 8.2203",
        "trọng 161",
        "triết 105.75",
        "luân 410",
        "phương 0.1652",
        "quốc 129.6667",
        "thông 76",
        "khiêm 366",
        "hòa 4.4321",
        "thanh 0.4069",
        "tường 0.8272",
        "kha 10.129",
        "vỹ 94.3333",
        "bách 140",
        "khanh 0.6532",
        "mạnh 137",
        "lợi 8.1563",
        "đại 244",
        "hiệp 26",
        "đông 16.7143",
        "nhựt 45.8",
        "giang 0.3136",
        "kỳ 0.5221",
        "phi 4.5319",
        "tấn 106",
        "văn 3.6071",
        "vương 39.8",
        "công 95.5",
        "hiển 94",
        "linh 0.0434",
        "ngọc 0.0349",
        "vĩ 58"
);
$gender = 'unknown'; // giả định ban đầu là chưa xác định giới tính 

// Gộp các mảng với nhau
$mid_pop = array_merge($mid_male_pop_ratio, $mid_female_pop_ratio); // chỉ số phân biệt giới của 100 đệm phổ biến của nam và nữ
$forename_pop = array_merge($forename_male_pop_ratio, $forename_female_pop_ratio); // chỉ số phân biệt giới của 100 tên phổ biến của nam và nữ

// Trường hợp nhập vào một từ
    if (vnn_word_count($name) == 1) {
            foreach ($forename_pop as $fore) {
                    $rs = mb_split(' ', $fore); // tách từ

                    if ($name == $rs[0]) {
                        $qfn = $rs[1]; break;} // $qfn cho ta giá trị của chỉ số phân biệt giới của tên vừa nhập
                    else {
                        $qfn = NULL;}    
            }

            if ($qfn!=NULL) {
                if ($qfn > 5) {$gender = "nam";}
                if (5 >= $qfn && $qfn >=1) {$gender = "nam-nữ";}

                if (0.2 > $qfn) {$gender = "nữ";}
                if ($qfn >= 0.2 && 1 > $qfn) {$gender = "nữ-nam";}
            }
    }


// Trường hợp nhập vào hai từ
    if (vnn_word_count($name) == 2) {
            $sp_name = mb_split(' ', $name); // tách từ

            $forename = $sp_name[1]; // tên chính
            $midname = $sp_name[0]; // đệm sát tên chính

            // tìm chỉ số phân biệt giới của đệm
            foreach ($mid_pop as $mid) {
                    $rs = mb_split(' ', $mid); // tách đệm và chỉ số
                    if ($midname == $rs[0]) {
                        $qmn = $rs[1]; break;} // $qmn cho ta giá trị của chỉ số phân biệt giới của ĐỆM vừa nhập
                    else {
                        $qmn = NULL;}    
            }
            
            // tìm chỉ số phân biệt giới của tên chính
            foreach ($forename_pop as $fore) {
                    $rs = mb_split(' ', $fore); // tách tên chính và chỉ số
                    if ($forename == $rs[0]) {
                        $qfn = $rs[1]; break;} // $qfn cho ta giá trị của chỉ số phân biệt giới của tên vừa nhập
                    else {
                        $qfn = NULL;}    
            }
            
            $cons_gender = 1.5; // hệ số giả định
            
            // bắt đầu phân tích nếu đệm tên nhập vào nằm trong nhóm phân tích được, tức là khác NULL
            if ($qmn!=NULL && $qfn!=NULL) {
                // khi cả 2 chỉ số đều có thiên hướng nam
                if ($qmn > 1 && $qfn > 1) {$gender = "nam";}
                
                // khi cả 2 chỉ số đều có thiên hướng nữ
                if (1 > $qmn && 1 > $qfn) {$gender = "nữ";}

                // khi đệm có thiên hướng nam, nhưng tên chính lại có thiên hướng nữ
                if ($qmn > 1 && 1 > $qfn) {
                    $reverse_ratio_forename = $cons_gender*(1/$qfn); // trường hợp tên chính có thiên hướng nữ
                    
                    if ($qmn > $reverse_ratio_forename) {
                        $gender = "nam";
                    } 
                    else {$gender = "nữ";
                    }
                }

                // khi đệm có thiên hướng nữ, nhưng tên chính lại có thiên hướng nam
                if (1 > $qmn && $qfn > 1) {
                    $reverse_ratio_midname = 1/$qmn; // trường hợp đệm có thiên hướng nữ
                    
                    if ($reverse_ratio_midname > $cons_gender*$qfn) {
                        $gender = "nữ";
                    } 
                    else {
                        $gender = "nam";
                    }
                }    
            }
    }

return $gender;    
}

Cách nó thực hiện

  • Nó trả về một trong 3 kết quả: nam, nữ, unknown;
  • Đầu vào có thể là một từ, ví dụ Tuấn, Đức, Thảo, Thanh, hoặc 2 từ như Đức Hùng, Bình Minh, Xuân Đạt, Minh Tuấn, Văn Vinh, nhưng cần chuyển trước về dạng viết thường như đức hùng, bình minh. Đoạn mã bổ sung ở cuối, cho phép dữ liệu đầu vào là dạng 4 từ;
  • Nó trả về unknown trong trường hợp đệm, tên hoặc cả 2 không nằm trong top 100 đệm tên phổ biến mà nó có thể phân tích được, unknown ở đây nghĩa là không xác định được;
  • 2 trường hợp dễ phân tích nhất là cả đệm và tên có cùng thiên hướng về một giới nhất định, tức là cả đệm và tên đều có thiên hướng giới là nam hoặc nữ;
  • 2 trường hợp phức tạp hơn đó là đệm và tên có thiên hướng giới đối lập nhau (đệm thiên hướng nam, tên chính thiên hướng nữ hoặc đệm thiên hướng nữ, còn tên chính lại thiên hướng nam). Hàm trên sử dụng hệ số $cons_gender = 1.5, nghĩa là trong trường hợp đệm và tên ngược thiên hướng nhau thì sau khi chuyển đổi một chút thì đệm cần có thiên hướng giới lớn hơn 1,5 lần tên chính thì đệm-tên đó mới mang giới của đệm, còn không sẽ mang giới của nam. Ví dụ Mạnh Linh, thì Mạnh có thiên hướng đệm nam tính với hệ số 108, Linh có thiên hướng nữ với hệ số 0.0434, đảo lại chỉ số này thì là 23 (1/0.0434) cho tên nữ. Và 108 > 1.5 * 23, do vậy tên này mang giới của đệm, nghĩa là nam;
  • Với trường hợp nhập vào là một từ, nếu hệ số > 5 là tên nam, nhỏ hơn 0.2 là tên nữ. Nằm trong khoảng 0.2 đến 1 là tên nữ-nam nghĩa là thường dùng cho nữ, nhưng nam cũng có thể dùng. Từ lớn hơn 1 đến 5 là nam-nữ, nghĩa là thường dùng cho nam nhưng nữ cũng có thể dùng;

Cải tiến

Hàm trên thực sự còn đơn giản, các điểm cần cải tiến bao gồm:

  • Mở rộng số lượng đệm tên có thể nhập vào thay vì chỉ 100 đệm tên phổ biến nhất cho nam và nữ. Để làm được điều này (vì không phải đệm tên nào cũng có chỉ số phân biệt giới đáng tin cậy) thì cần từ điển giải nghĩa họ tên tốt, căn cứ vào nghĩa ta có thể gán cho nó việc nó thường được dùng cho nam hay nữ hơn;
  • Mở rộng khả năng người dùng nhập vào đầy đủ họ tên. Với trường hợp nhập đầy đủ họ tên, ví dụ như: Nguyễn Bảo Như Ý, chương trình vẫn cần xử lý được. Các trường hợp nhập đầy đủ có khả năng dễ phát hiện chính xác hơn, vì lượng dữ liệu để phân tích nhiều hơn, nhập nhằng giảm xuống;
  • Cải tiến thuật toán bằng cách kiểm tra ngược với các mẫu họ tên sẵn có, để xem nó có độ chính xác đến đâu, trước tiên điều chỉnh hệ số $cons_gender xem giá trị nào cho tỷ lệ đúng cao nhất;

Xin chào và hẹn gặp lại các bạn trong bài viết kiểm tra ngược để điều chỉnh thuật toán nhằm cho kết quả chính xác hơn…


Cập nhật:

PS1: Tôi vừa kiểm tra ngược xong, $cons_gender không ảnh hưởng quá lớn đến kết quả, thay đổi số này trong khoảng từ 1 đến 5 chỉ thay đổi 2% độ chính xác, giá trị tối ưu mà tôi dò được là 1.3

Ngoài ra bổ sung đoạn mã nhỏ bên dưới tăng thêm độ chính xác khoảng 2%:

// trường hợp đệm là NULL và tên chính có chỉ số phân biệt giới lớn hơn 12
    if ($qmn==NULL && $qfn!=NULL) {
                if ($qfn > 12) {$gender = "nam";}
                if (1/$qfn > 12) {$gender = "nữ";}
    }
       
     
// trường hợp tên chính là NULL và tên đệm có chỉ số phân biệt giới lớn hơn 20
    if ($qmn!=NULL && $qfn==NULL) {
                if ($qmn > 10) {$gender = "nam";}
                if (1/$qmn > 10) {$gender = "nữ";}
    }

Tổng hợp lại khi kiểm tra với mẫu họ tên người SG01, hàm trên cho kết quả chính xác là 92.8% – nói chung cũng tạm ổn, nâng lên được tầm hơn 97% thì có lẽ sẽ đủ tiêu chuẩn. Tuy nhiên vì hàm này phát triển từ chính mẫu SG01 nên nó cần kiểm tra thêm độ chính xác ở các mẫu khác, rồi sau đó cải tiến thêm từ các mẫu ấy, khi đó tính tổng quát mới cao được.

PS2: Trường hợp người dùng nhập vào đầy đủ họ tên gồm 4 từ (tức là có cả đệm 1 và đệm 2), đoạn mã bên dưới cải thiện độ chính xác khoảng 2%:

    // Trường hợp nhập vào bốn từ, thường nghĩa là nhập cả họ + đệm 1 + đệm 2 + tên chính
    if (vnn_word_count($name) == 4) {
      
            $sp_name = mb_split(' ', $name); // tách từ

            $forename = $sp_name[3]; // tên chính
            $midname2 = $sp_name[2]; // đệm sát tên chính (đệm hai)
            $midname1 = $sp_name[1]; // đệm thứ nhất
            
            // tìm chỉ số phân biệt giới của đệm 1
            foreach ($mid_pop as $mid) {
                    $rs = mb_split(' ', $mid); // tách đệm và chỉ số
                    if ($midname1 == $rs[0]) {
                        $qmn = $rs[1]; break;} // $qmn cho ta giá trị của chỉ số phân biệt giới của ĐỆM vừa nhập
                    else {
                        $qmn = 1;}    
            }
            
            // tìm chỉ số phân biệt giới của đệm 2
            foreach ($mid_pop as $mid2) {
                    $rs = mb_split(' ', $mid2); // tách đệm và chỉ số
                    if ($midname2 == $rs[0]) {
                        $qmn2 = $rs[1]; break;} // $qmn cho ta giá trị của chỉ số phân biệt giới của ĐỆM vừa nhập
                    else {
                        $qmn2 = 1;}    
            }
            
            // tìm chỉ số phân biệt giới của tên chính
            foreach ($forename_pop as $fore) {
                    $rs = mb_split(' ', $fore); // tách tên chính và chỉ số
                    if ($forename == $rs[0]) {
                        $qfn = $rs[1]; break;} // $qfn cho ta giá trị của chỉ số phân biệt giới của tên vừa nhập
                    else {
                        $qfn = 1;}    
            }
            
            if ($qmn * $qmn2 * $qfn > 1) {$gender = "nam";}
            if ($qmn * $qmn2 * $qfn < 1) {$gender = "nữ";}
    }

Với dữ liệu nhập vào 4 từ, công thức đơn giản hơn khá nhiều nhưng lại cho độ chính xác cao hơn.


Ứng dụng thực tế: hàm trên với một số cải tiến khác, đã được sử dụng để làm hàm xác định giới tính của họ tên người phổ biến cho trang bầu.com (website về gợi ý tên con).

Back to Top