KTV歌曲推薦-邏輯迴歸-用戶性別預測

前言

上一篇寫了推薦系統最古老的的一種算法叫協同過濾,古老並非很差用,其實仍是很好用的一種算法,隨着時代的進步,出現了神經網絡和因子分解等更優秀的算法解決不一樣的問題。 這裏主要說一下邏輯迴歸,邏輯迴歸主要用於打分的預估。我這裏沒有打分的數據因此用性別代替。 這裏的例子就是用歌曲列表預判用戶性別。python

什麼是邏輯迴歸

邏輯迴歸的資料比較多,我比較推薦你們看刷一下bilibili上李宏毅老師的視頻,這裏我只說一些須要注意的點。算法

網絡結構

邏輯迴歸能夠理解爲一種單層神經網絡,網絡結構如圖: 網絡

激活函數選擇

邏輯迴歸通常選sigmoid或者softmaxdom

  • 圖的上半部分就是二元邏輯迴歸激活函數是sigmoid
  • 圖的下半部分是多元邏輯迴歸沒有激活函數直接接了一個softmax

別問我啥是sigmoid啥是softmax,問就是百度。elasticsearch

損失函數選擇

損失函數邏輯迴歸經常使用的有三種(其實有不少不止三種,本身查API嘍):函數

  • binary_crossentropy
  • categorical_crossentropy
  • sparse_categorical_crossentrop 這裏其實用binary更合適,可是我這裏選的categorical_crossentropy,由於我懶得改了,並且我後面會作其餘功能

梯度降低選擇

梯度降低方式有不少,我這裏選擇隨機梯度降低,sgd其實我以爲adam更合適,看你們心情了。至於爲啥工具

數據準備

此次的數據是1萬條KTV唱歌數據,別問我數據哪來的。問就是別人給的。測試

X是用戶唱歌數據的one-hot優化

Y是用戶的性別one-hotui

下面是真正的技術

代碼實現

  • 數據拆分爲 80%訓練 20%測試
  • 這裏雖然只有兩類可是仍是用了softmax,不影響
  • 訓練工具是keras

數據獲取

下面代碼都幹了些啥呢,主要是兩個matrix。

一個是用戶唱歌的onehot->song_hot_matrix。

一個是用戶性別的onehot->decades_hot_matrix。 代碼不重要,主要看字。

import elasticsearch
import elasticsearch.helpers
import re
import numpy as np
import operator
import datetime


es_client = elasticsearch.Elasticsearch(hosts=["localhost:9200"])

def trim_song_name(song_name):
    """
    處理歌名,過濾掉無用內容和空白
    """
    song_name = song_name.strip()
    song_name = re.sub("【.*?】", "", song_name)
    song_name = re.sub("(.*?)", "", song_name)
    return song_name

def trim_address_name(address_name):
    """
    處理地址
    """
    return str(address_name).strip()

def get_data(size=0):
    """
    獲取uid=>做品名list的字典
    """
    cur_size=0
    song_dic = {}
    user_address_dic = {}
    user_decades_dic = {}
    
    search_result = elasticsearch.helpers.scan(
        es_client, 
        index="ktv_user_info", 
        doc_type="ktv_works", 
        scroll="10m",
        query={
            "query":{
                "range": {
                    "birthday": {
                        "gt": 63072662400
                    }
                }
            }
        }
    )

    for hit_item in search_result:
        cur_size += 1
        if size>0 and cur_size>size:
            break
            
        user_info = hit_item["_source"]
        item = get_work_info(hit_item["_id"])
        if item is None:
            continue

        work_list = item['item_list']
        if len(work_list)<2:
            continue
        
        if user_info['gender']==0:
            continue
        if user_info['gender']==1:
            user_info['gender']="男"
        if user_info['gender']==2:
            user_info['gender']="女"
        
        song_dic[item['uid']] = [trim_song_name(item['songname']) for item in work_list]

        
        user_decades_dic[item['uid']] = user_info['gender']
        user_address_dic[item['uid']] = trim_address_name(user_info['address'])
        
    return (song_dic, user_address_dic, user_decades_dic)

def get_user_info(uid):
    """
    獲取用戶信息
    """
    ret = es_client.get(
        index="ktv_user_info", 
        doc_type="ktv_works", 
        id=uid
    )
    return ret['_source']

def get_work_info(uid):
    """
    獲取用戶信息
    """
    try:
        ret = es_client.get(
            index="ktv_works", 
            doc_type="ktv_works", 
            id=uid
        )
        return ret['_source']
    except Exception as ex:
        return None


def get_uniq_song_sort_list(song_dict):
    """
    合併重複歌曲並按歌曲名排序
    """
    return sorted(list(set(np.concatenate(list(song_dict.values())).tolist())))
    
from sklearn import preprocessing
%run label_encoder.ipynb

user_count = 4000
song_count = 0

# 得到用戶唱歌數據
song_dict, user_address_dict, user_decades_dict  = get_data(user_count)

# 歌曲字典
song_label_encoder = LabelEncoder()
song_label_encoder.fit_dict(song_dict, "", True)
song_hot_matrix = song_label_encoder.encode_hot_dict(song_dict, True)

user_decades_encoder = LabelEncoder()
user_decades_encoder.fit_dict(user_decades_dict)
decades_hot_matrix = user_decades_encoder.encode_hot_dict(user_decades_dict, False)

song_hot_matrix

uid 洗刷刷 麻雀 你的答案
0 0 1 0
1 1 1 0
2 1 0 0
3 0 0 0

decades_hot_matrix

uid
0 1 0
1 0 1
2 1 0
3 0 1

模型訓練

import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Activation, Embedding,Flatten
import matplotlib.pyplot as plt
from keras.utils import np_utils
from sklearn import datasets
from sklearn.model_selection import train_test_split

n_class=user_decades_encoder.get_class_count()
song_count=song_label_encoder.get_class_count()
print(n_class)
print(song_count)

# 拆分訓練數據和測試數據
train_X,test_X, train_y, test_y = train_test_split(song_hot_matrix,
                                                   decades_hot_matrix,
                                                   test_size = 0.2,
                                                   random_state = 0)
train_count = np.shape(train_X)[0]
# 構建神經網絡模型
model = Sequential()
model.add(Dense(input_dim=8, units=n_class))
model.add(Activation('softmax'))

# 選定loss函數和優化器
model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy'])

# 訓練過程
print('Training -----------')
for step in range(train_count):
    scores = model.train_on_batch(train_X, train_y)
    if step % 50 == 0:
        print("訓練樣本 %d 個, 損失: %f, 準確率: %f" % (step, scores[0], scores[1]*100))
print('finish!')

準確率測試集評估

數據訓練完了用拆分出來的20%數據測試一下:

# 準確率評估
from sklearn.metrics import classification_report
scores = model.evaluate(test_X, test_y, verbose=0)
print("%s: %.2f%%" % (model.metrics_names[1], scores[1]*100))
Y_test = np.argmax(test_y, axis=1)
y_pred = model.predict_classes(test_X)
print(classification_report(Y_test, y_pred))

輸出:

accuracy: 78.43%
              precision    recall  f1-score   support

           0       0.72      0.90      0.80       220
           1       0.88      0.68      0.77       239

    accuracy                           0.78       459
   macro avg       0.80      0.79      0.78       459
weighted avg       0.80      0.78      0.78       459

人工測試

而後讓小夥伴們一塊兒來玩耍,嗯準確率100%,完美!

def pred(song_list=[]):
    blong_hot_matrix = song_label_encoder.encode_hot_dict({"bblong":song_list}, True)
    y_pred = model.predict_classes(blong_hot_matrix)
    return user_decades_encoder.decode_list(y_pred)

# # 男A
# print(pred(["一路向北", "暗香", "菊花臺"]))
# # 男B
# print(pred(["不要說話", "平凡之路", "李白"]))
# # 女A
# print(pred(["滿足", "被風吹過的夏天", "龍捲風"]))
# # 男C
# print(pred(["情人","再見","無賴","離人","你的樣子"]))
# # 男D
# print(pred(["小情歌","我好想你","無與倫比的美麗"]))
# # 男E
# print(pred(["忐忑","最炫民族風","小蘋果"]))
相關文章
相關標籤/搜索