├── .gitignore ├── freq.png ├── view.png ├── view-buy.png ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /freq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skyer9/collaborative-filtering-in-mysql/HEAD/freq.png -------------------------------------------------------------------------------- /view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skyer9/collaborative-filtering-in-mysql/HEAD/view.png -------------------------------------------------------------------------------- /view-buy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skyer9/collaborative-filtering-in-mysql/HEAD/view-buy.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 skyer9 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Collaborative Filtering In MySQL 2 | 3 | 협업 필터링(collaborative filtering)은 친숙하게 사용중인 SQL 로도 가능합니다. 4 | 5 | ```sql 6 | CREATE TABLE `tbl_order` ( 7 | `orderserial` varchar(32) NOT NULL, 8 | `userid` varchar(32) NOT NULL, 9 | `itemid` int(11) NOT NULL, 10 | PRIMARY KEY (`orderserial`,`userid`,`itemid`) 11 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 12 | ``` 13 | 14 | 주문 테이블을 간소화하면 위와 같은 테이블이 됩니다. 15 | 16 | ```sql 17 | insert into tbl_order(orderserial, userid, itemid) 18 | values ('20190701000000001', 'A', 1), ('20190701000000001', 'A', 2), ('20190701000000001', 'A', 3); 19 | 20 | insert into tbl_order(orderserial, userid, itemid) 21 | values ('20190701000000002', 'B', 1), ('20190701000000002', 'B', 4); 22 | 23 | insert into tbl_order(orderserial, userid, itemid) 24 | values ('20190701000000003', 'C', 2), ('20190701000000003', 'C', 3), ('20190701000000003', 'C', 5); 25 | 26 | insert into tbl_order(orderserial, userid, itemid) 27 | values ('20190701000000004', 'D', 1), ('20190701000000004', 'D', 2), ('20190701000000004', 'D', 6); 28 | ``` 29 | 30 | 샘플 데이타를 입력해 봅니다. 31 | 32 | ```sql 33 | select A.itemid, B.itemid, count(B.itemid) as cnt 34 | from 35 | tbl_order A 36 | join tbl_order B 37 | on 38 | 1 = 1 39 | and A.orderserial = B.orderserial 40 | and A.userid = B.userid 41 | and A.itemid <> B.itemid 42 | group by 43 | A.itemid, B.itemid 44 | order by 45 | A.itemid, count(B.itemid) desc; 46 | ``` 47 | 48 | 복잡한 공식없이 익숙한 SQL 만으로 `같이 구매한 상품` 데이타가 생성되었습니다. 49 | 50 | 고객 상품조회 로그를 저장하는 서버가 있는 경우, 위 쿼리는 데이타만 바꿔주면 `같이 본 상품` 데이타 생성도 가능합니다. 51 | 52 | ```sql 53 | CREATE TABLE `tbl_weblog` ( 54 | `yyyymmddhhmmss` varchar(32) NOT NULL, 55 | `userid` varchar(32) NOT NULL, 56 | `itemid` int(11) NOT NULL, 57 | PRIMARY KEY (`yyyymmddhhmmss`,`userid`,`itemid`) 58 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 59 | ``` 60 | 61 | 웹로그는 아래와 같이 정제할 수 있습니다. 62 | 63 | ```sql 64 | insert into tbl_weblog(yyyymmddhhmmss, userid, itemid) 65 | values ('2019-07-01 11:22:33', 'A', 1), ('2019-07-01 12:22:33', 'A', 2), ('2019-07-01 13:22:33', 'A', 3); 66 | 67 | insert into tbl_weblog(yyyymmddhhmmss, userid, itemid) 68 | values ('2019-07-01 11:40:33', 'B', 1), ('2019-07-01 11:41:33', 'B', 4); 69 | 70 | insert into tbl_weblog(yyyymmddhhmmss, userid, itemid) 71 | values ('2019-07-01 09:22:33', 'C', 2), ('2019-07-01 09:55:33', 'C', 3), ('2019-07-01 09:59:33', 'C', 5); 72 | 73 | insert into tbl_weblog(yyyymmddhhmmss, userid, itemid) 74 | values ('2019-07-01 15:22:33', 'D', 1), ('2019-07-01 16:22:33', 'D', 2), ('2019-07-01 17:22:33', 'D', 6); 75 | ``` 76 | 77 | 같은 날짜에 본 상품을 같이 본 상품으로 정하면 동일한 쿼리를 적용할 수 있습니다. 78 | 79 | ```sql 80 | select A.itemid, B.itemid, count(B.itemid) as cnt 81 | from 82 | tbl_weblog A 83 | join tbl_weblog B 84 | on 85 | 1 = 1 86 | and left(A.yyyymmddhhmmss, 10) = left(B.yyyymmddhhmmss, 10) 87 | and A.userid = B.userid 88 | and A.itemid <> B.itemid 89 | group by 90 | A.itemid, B.itemid 91 | order by 92 | A.itemid, count(B.itemid) desc; 93 | ``` 94 | 95 | 여기까지는 협업 필터링을 SQL 로 구현한 것에 불과하고 실제로 서비스에는 사용할 수 없는 데이타입니다. 96 | 97 | `같이 구매한 상품` 을 예로 들어봅니다. 우리가 원하는 같이 구매한 상품은 "노트북을 살 때 마우스도 같이 살 것이다" 라는 생각이지만 현실은 베스트셀러가 같이 구매한 상품이 되는 경우가 더 많습니다. 98 | 99 | 간단한 예로 엑셀 도서를 구매하려는 고객이 있습니다. 그런데 메인 페이지에 삼겹살이 특가로 세일을 하여 그것을 본 고객은 엑셀도서와 삼겹살을 같이 구매하게 되는거죠. 100 | 101 | 위 문제를 해결하는 가장 간단한 방법은 카테고리 제한이었습니다. 102 | 103 | ```sql 104 | CREATE TABLE `tbl_order_v2` ( 105 | `orderserial` varchar(32) NOT NULL, 106 | `userid` varchar(32) NOT NULL, 107 | `category` varchar(32) NOT NULL, 108 | `itemid` int(11) NOT NULL, 109 | PRIMARY KEY (`orderserial`,`userid`,`itemid`) 110 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 111 | 112 | insert into tbl_order_v2(orderserial, userid, category, itemid) 113 | values ('20190701000000001', 'A', 'book', 1), ('20190701000000001', 'A', 'book', 2), ('20190701000000001', 'A', 'food', 3); 114 | 115 | insert into tbl_order_v2(orderserial, userid, category, itemid) 116 | values ('20190701000000002', 'B', 'book', 1), ('20190701000000002', 'B', 'food', 4); 117 | 118 | insert into tbl_order_v2(orderserial, userid, category, itemid) 119 | values ('20190701000000003', 'C', 'book', 2), ('20190701000000003', 'C', 'food', 3), ('20190701000000003', 'C', 'food', 5); 120 | 121 | insert into tbl_order_v2(orderserial, userid, category, itemid) 122 | values ('20190701000000004', 'D', 'book', 1), ('20190701000000004', 'D', 'book', 2), ('20190701000000004', 'D', 'food', 6); 123 | 124 | select A.itemid, B.itemid, count(B.itemid) as cnt 125 | from 126 | tbl_order_v2 A 127 | join tbl_order_v2 B 128 | on 129 | 1 = 1 130 | and A.orderserial = B.orderserial 131 | and A.userid = B.userid 132 | and A.category = B.category 133 | and A.itemid <> B.itemid 134 | group by 135 | A.itemid, B.itemid 136 | order by 137 | A.itemid, count(B.itemid) desc; 138 | ``` 139 | 140 | 협업 필터링은 몇가지 내재된 단점이 존재합니다. 141 | 142 | 우선, 콜드 스타트(Cold Start) 입니다. 143 | 같이 구매한 상품 데이타를 구하려 할 때 오늘 등록된 신상품은 추천할 데이타가 없습니다. 144 | 이 문제를 해결하기 위해 실시간 또는 시간단위 업데이트를 통해 추천 데이타가 없는 시간을 단축시키는 방법이 동원됩니다. 145 | 146 | 또, 롱테일(Long tail) 문제입니다. 147 | 많은 종류의 상품이 있다고 할 때 고객들은 일부 상품에 집중하는 경향이 있습니다. 148 | 이러한 상품들 중 가장 많이 팔린 상품이 베스트셀러가 되는거죠. 149 | 문제는 40% 이상의 상품이 구매에서 배제되고 당연히 `같이 구매한 상품` 에도 제외됩니다. 150 | 151 | 협업 필터링은 매우 간단하지만 또 매우 강력한 추천시스템입니다. 152 | 153 | 위에서는 상품정보 중 카테고리 정보만 추가하여 필터링을 고도화하였는데, 상품정보에는 많은 정보가 있습니다. 154 | 브랜드, 가격대, 색상 등 여러 정보를 추가하면 넷플릭스 같은 고도화된 추천시스템을 구현할 수 있을겁니다. 155 | 156 | ~~"추천시스템? 나는 그런거 몰라" 이러고 있으실 개발자를 위해.~~ 157 | --------------------------------------------------------------------------------